rubric/modules/gomodules/go_composite.go
2026-07-21 14:52:55 +03:00

617 lines
22 KiB
Go
Executable file
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.

package gomodules
import (
"fmt"
"rubric/utils"
)
var CompositeModule = Module{
Key: "composite",
Aliases: []string{"c", "comp", "slice", "map", "struct"},
Title: "Составные типы",
Description: "Массивы, срезы, append, отображения, структуры, JSON и HTML",
Run: showComposite,
}
func showComposite() {
utils.Header("Go — Составные типы")
// ─── Массивы ──────────────────────────────────────────────────
utils.Section("Массивы (Arrays)")
utils.Println("Массив — фиксированной длины последовательность элементов одного типа.")
utils.Println("Длина является частью типа: " +
utils.ColorBold + utils.ColorCyan + "[3]int" + utils.ColorReset +
" и " +
utils.ColorBold + utils.ColorCyan + "[4]int" + utils.ColorReset +
" — разные типы.")
fmt.Println()
utils.Code(`// Объявление:
var a [3]int // [0 0 0] — нулевые значения
var b [3]int = [3]int{1, 2, 3}
c := [3]int{1, 2, 3}
d := [...]int{1, 2, 3, 4, 5} // компилятор считает длину сам
// Инициализация с индексами:
e := [5]string{1: "один", 3: "три"} // остальные — ""
// Доступ:
fmt.Println(c[0]) // 1
fmt.Println(len(c)) // 3
// Итерация:
for i, v := range c {
fmt.Printf("c[%d] = %d\n", i, v)
}`)
utils.Println(utils.ColorBold + "Сравнение массивов:" + utils.ColorReset)
utils.Code(`// Массивы можно сравнивать через == и !=
// (если элементы сравниваемы):
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
fmt.Println(a == b) // true
// Передача в функцию — КОПИЯ:
func double(arr [3]int) { arr[0] *= 2 } // оригинал не меняется
// Чтобы изменить — передавать указатель: *[3]int`)
utils.Note("Массивы в Go используются редко — чаще работают со срезами.")
// ─── Срезы ────────────────────────────────────────────────────
utils.Section("Срезы (Slices)")
utils.Println("Срез — динамический вид на массив. Три компонента:")
utils.Code(`// Указатель на массив + длина (len) + ёмкость (cap)
//
// ┌─────────────────────────────────────────┐
// │ ptr │ len │ cap │
// └───┬───┴───────┴─────────────────────────┘
// │
// ▼
// ┌───┬───┬───┬───┬───┐
// │ 0 │ 1 │ 2 │ 3 │ 4 │ ← underlying array
// └───┴───┴───┴───┴───┘`)
utils.Println(utils.ColorBold + "Создание срезов:" + utils.ColorReset)
utils.Code(`// Из литерала (неявно создаёт массив):
s := []int{1, 2, 3, 4, 5}
// Срез нулевых значений через make(тип, len, cap):
s := make([]int, 5) // len=5, cap=5
s := make([]int, 3, 10) // len=3, cap=10
// Нулевой срез (nil slice):
var s []int // s == nil, len=0, cap=0
fmt.Println(s == nil) // true
// nil срез безопасен для append и range
// Срез из массива:
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4] // элементы 1,2,3 → [2 3 4]
// len=3, cap=4 (до конца массива)`)
utils.Println(utils.ColorBold + "Операции срезов:" + utils.ColorReset)
utils.Code(`s := []int{1, 2, 3, 4, 5}
// Взятие среза: s[low:high] (low включён, high — нет)
s[1:3] // [2 3] len=2
s[:3] // [1 2 3] low=0
s[2:] // [3 4 5] high=len
s[:] // копия ссылки на весь срез
// Трёхиндексный срез: s[low:high:max] — ограничивает cap:
s[1:3:4] // len=2, cap=3
// len и cap:
fmt.Println(len(s), cap(s)) // 5 5
// Срезы разделяют память с оригиналом!
a := []int{1, 2, 3, 4, 5}
b := a[1:3] // b = [2 3]
b[0] = 99
fmt.Println(a) // [1 99 3 4 5] — оригинал изменён!`)
utils.Println(utils.ColorBold + "Копирование срезов (copy):" + utils.ColorReset)
utils.Code(`src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // копирует min(len(dst), len(src)) элементов
fmt.Println(dst, n) // [1 2 3] 3
// Полная независимая копия:
clone := make([]int, len(src))
copy(clone, src)
// Или через append:
clone := append([]int{}, src...)`)
utils.Println(utils.ColorBold + "Итерация:" + utils.ColorReset)
utils.Code(`s := []string{"a", "b", "c"}
for i, v := range s { // v — копия элемента
fmt.Println(i, v)
}
for i := range s { // только индексы
s[i] = strings.ToUpper(s[i])
}
for _, v := range s { // только значения
fmt.Println(v)
}`)
// ─── append ───────────────────────────────────────────────────
utils.Section("Функция append")
utils.Println("append добавляет элементы в срез, возвращая новый (возможно, с новым массивом).")
utils.Code(`// Добавить элементы:
s := []int{1, 2, 3}
s = append(s, 4) // [1 2 3 4]
s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7]
// Добавить другой срез (оператор ...):
other := []int{8, 9}
s = append(s, other...) // [1 2 3 4 5 6 7 8 9]
// Удалить элемент по индексу i:
i := 2
s = append(s[:i], s[i+1:]...) // порядок сохраняется
// Быстрое удаление (порядок не важен):
s[i] = s[len(s)-1]
s = s[:len(s)-1]
// Вставить элемент по индексу i:
s = append(s[:i+1], s[i:]...)
s[i] = newValue
// Стек (stack):
push := func(s []int, v int) []int { return append(s, v) }
pop := func(s []int) ([]int, int) { return s[:len(s)-1], s[len(s)-1] }`)
utils.Println(utils.ColorBold + "Механизм роста ёмкости:" + utils.ColorReset)
utils.Code(`// При нехватке cap, append выделяет новый массив (~удваивает cap):
var s []int
for i := 0; i < 8; i++ {
s = append(s, i)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
// len=1 cap=1 [0]
// len=2 cap=2 [0 1]
// len=3 cap=4 [0 1 2]
// len=4 cap=4 [0 1 2 3]
// len=5 cap=8 [0 1 2 3 4]
// ...`)
utils.Note("Всегда присваивай результат append обратно: s = append(s, v)")
utils.Note("Передавай срез с запасом через make([]T, 0, n) если знаешь итоговый размер — избежишь лишних аллокаций.")
// ─── Отображения ──────────────────────────────────────────────
utils.Section("Отображения (Maps)")
utils.Println("Map — ссылочный тип, хэш-таблица. Порядок итерации не гарантирован.")
fmt.Println()
utils.Println(utils.ColorBold + "Создание:" + utils.ColorReset)
utils.Code(`// Через литерал:
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
// Через make:
ages := make(map[string]int)
ages := make(map[string]int, 100) // с подсказкой ёмкости
// Нулевое значение — nil:
var m map[string]int // m == nil
// m["key"] = 1 // паника! нельзя писать в nil map
// fmt.Println(m["key"]) // 0 — читать можно, не паникует`)
utils.Println(utils.ColorBold + "Операции с map:" + utils.ColorReset)
utils.Code(`ages := map[string]int{"Alice": 30, "Bob": 25}
// Запись:
ages["Carol"] = 28
ages["Alice"] = 31 // обновить
// Чтение:
age := ages["Alice"] // 31
age = ages["unknown"] // 0 — нулевое значение, НЕ ошибка
// Проверка наличия ключа (comma ok idiom):
age, ok := ages["Alice"]
if ok {
fmt.Println("Alice:", age)
}
// Коротко:
if age, ok := ages["Alice"]; ok {
fmt.Println("Alice:", age)
}
// Удаление:
delete(ages, "Bob")
delete(ages, "nonexistent") // безопасно, нет ошибки
// Длина:
fmt.Println(len(ages)) // 2
// Итерация (порядок случаен!):
for name, age := range ages {
fmt.Printf("%s: %d\n", name, age)
}
// Итерация по отсортированным ключам:
keys := make([]string, 0, len(ages))
for k := range ages { keys = append(keys, k) }
sort.Strings(keys)
for _, k := range keys { fmt.Println(k, ages[k]) }`)
utils.Println(utils.ColorBold + "Сравнение map:" + utils.ColorReset)
utils.Code(`// Map нельзя сравнивать через == (только с nil):
var m map[string]int
fmt.Println(m == nil) // true
// Сравнение содержимого — через reflect.DeepEqual или вручную:
import "reflect"
fmt.Println(reflect.DeepEqual(map1, map2))
// Или maps.Equal из Go 1.21:
import "maps"
fmt.Println(maps.Equal(map1, map2))`)
utils.Println(utils.ColorBold + "Типичные паттерны:" + utils.ColorReset)
utils.Code(`// Множество (set):
seen := make(map[string]bool)
seen["Alice"] = true
if seen["Alice"] { ... }
// Подсчёт слов:
words := strings.Fields(text)
count := make(map[string]int)
for _, w := range words { count[w]++ }
// Группировка:
byDept := make(map[string][]Employee)
for _, e := range employees {
byDept[e.Dept] = append(byDept[e.Dept], e)
}`)
// ─── Структуры ────────────────────────────────────────────────
utils.Section("Структуры (Structs)")
utils.Println("Struct — агрегатный тип, объединяет поля разных типов.")
utils.Code(`// Объявление типа:
type Point struct {
X, Y float64
}
type Employee struct {
ID int
Name string
Salary float64
Manager *Employee // рекурсивный тип — только указатель
}`)
utils.Println(utils.ColorBold + "Создание и доступ:" + utils.ColorReset)
utils.Code(`// Структурный литерал (по именам — рекомендуется):
p := Point{X: 1.0, Y: 2.0}
p := Point{1.0, 2.0} // по позиции (хрупко, не для публичных типов)
// Нулевое значение:
var p Point // Point{X:0, Y:0}
// Доступ к полям:
fmt.Println(p.X) // 1.0
p.Y = 5.0
// Указатель на структуру:
pp := &Point{1.0, 2.0}
pp.X = 10 // (*pp).X = 10 — автодеференс
fmt.Println(pp.X) // 10
// Через new:
pp := new(Point) // *Point, поля = нули
pp.X = 1.0`)
utils.Println(utils.ColorBold + "Сравнение структур:" + utils.ColorReset)
utils.Code(`// Структуры сравниваемы если все поля сравниваемы:
type Point struct{ X, Y int }
p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println(p1 == p2) // true
// Поля-срезы, map, func — делают структуру несравниваемой.
// Используй reflect.DeepEqual для глубокого сравнения.
// Структура как ключ map (только если сравниваема):
type Coord struct{ Lat, Lon float64 }
visited := map[Coord]bool{}
visited[Coord{55.75, 37.62}] = true`)
// ─── Встраивание и анонимные поля ────────────────────────────
utils.Section("Встраивание структур и анонимные поля")
utils.Println("Встраивание (embedding) — включение типа без имени поля.")
utils.Println("Поля и методы встроенного типа «поднимаются» во внешний тип.")
utils.Code(`type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " говорит"
}
type Dog struct {
Animal // анонимное поле — встраивание
Breed string
}
d := Dog{
Animal: Animal{Name: "Шарик"},
Breed: "Лабрадор",
}
// Продвинутый доступ (promotion):
fmt.Println(d.Name) // "Шарик" (d.Animal.Name)
fmt.Println(d.Speak()) // "Шарик говорит" (d.Animal.Speak())
fmt.Println(d.Breed) // "Лабрадор"
// Явный доступ:
fmt.Println(d.Animal.Name)`)
utils.Code(`// Несколько встраиваний:
type Logger struct{ Prefix string }
func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) }
type Server struct {
Logger // анонимное поле
net.Listener // встроен интерфейс
Host string
}
s := Server{Logger: Logger{Prefix: "[server]"}, Host: "localhost"}
s.Log("started") // s.Logger.Log("started")
// Конфликт имён — нужно явно указывать путь:
type A struct{ X int }
type B struct{ X int }
type C struct { A; B }
c := C{}
// c.X — ошибка компиляции: ambiguous
c.A.X = 1 // явно`)
utils.Code(`// Встраивание указателя:
type Cache struct {
*sync.Mutex // анонимный указатель
data map[string]string
}
c := Cache{
Mutex: &sync.Mutex{},
data: make(map[string]string),
}
c.Lock() // c.Mutex.Lock()
c.data["key"] = "value"
c.Unlock()`)
utils.Note("Встраивание — это композиция, не наследование. Go не имеет наследования.")
// ─── Работа с JSON ────────────────────────────────────────────
utils.Section("Работа с JSON")
utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "encoding/json" + utils.ColorReset +
" — маршалинг и демаршалинг JSON.")
fmt.Println()
utils.Println(utils.ColorBold + "Теги структур:" + utils.ColorReset)
utils.Code(`type Person struct {
Name string ` + "`" + `json:"name"` + "`" + `
Age int ` + "`" + `json:"age"` + "`" + `
Email string ` + "`" + `json:"email,omitempty"` + "`" + ` // пропустить если пусто
Phone string ` + "`" + `json:"-"` + "`" + ` // никогда не включать
private string // неэкспортируемые — игнорируются
}
// Теги:
// json:"name" — имя в JSON
// json:"name,omitempty"— пропустить нулевые значения
// json:"-" — исключить поле`)
utils.Println(utils.ColorBold + "Маршалинг (структура → JSON):" + utils.ColorReset)
utils.Code(`import "encoding/json"
p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}
// Сериализация:
data, err := json.Marshal(p)
if err != nil { log.Fatal(err) }
fmt.Println(string(data))
// {"name":"Alice","age":30,"email":"alice@example.com"}
// Красивый вывод с отступами:
data, err = json.MarshalIndent(p, "", " ")
fmt.Println(string(data))
// {
// "name": "Alice",
// "age": 30,
// "email": "alice@example.com"
// }`)
utils.Println(utils.ColorBold + "Демаршалинг (JSON → структура):" + utils.ColorReset)
utils.Code(`jsonStr := ` + "`" + `{"name":"Bob","age":25}` + "`" + `
var p Person
if err := json.Unmarshal([]byte(jsonStr), &p); err != nil {
log.Fatal(err)
}
fmt.Println(p.Name, p.Age) // Bob 25
// Потоковое чтение из http.Response:
resp, _ := http.Get("https://api.example.com/users")
defer resp.Body.Close()
var users []Person
json.NewDecoder(resp.Body).Decode(&users)
// Потоковая запись в http.ResponseWriter:
json.NewEncoder(w).Encode(users)`)
utils.Println(utils.ColorBold + "Произвольный JSON (interface{} / any):" + utils.ColorReset)
utils.Code(`// Когда структура неизвестна:
var result map[string]any
json.Unmarshal(data, &result)
name := result["name"].(string) // type assertion
age := result["age"].(float64) // числа → float64 по умолчанию
// Проверка типа:
if s, ok := result["name"].(string); ok {
fmt.Println(s)
}
// json.RawMessage — отложенный демаршалинг:
type Response struct {
Status string ` + "`" + `json:"status"` + "`" + `
Payload json.RawMessage ` + "`" + `json:"payload"` + "`" + `
}
var resp Response
json.Unmarshal(data, &resp)
var user Person
json.Unmarshal(resp.Payload, &user)`)
utils.Println(utils.ColorBold + "Кастомные маршалеры:" + utils.ColorReset)
utils.Code(`// Реализовать json.Marshaler / json.Unmarshaler:
type Duration time.Duration
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil { return err }
dur, err := time.ParseDuration(s)
*d = Duration(dur)
return err
}`)
// ─── Работа с HTML ────────────────────────────────────────────
utils.Section("Работа с HTML")
utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "html/template" + utils.ColorReset +
" — безопасные HTML-шаблоны с автоматическим экранированием.")
fmt.Println()
utils.Code(`import "html/template"
// Шаблон:
const tmpl = ` + "`" + `
<!DOCTYPE html>
<html>
<body>
<h1>Привет, {{.Name}}!</h1>
<ul>
{{range .Items}}
<li>{{.}}</li>
{{end}}
</ul>
{{if .Admin}}
<p>Вы администратор</p>
{{end}}
</body>
</html>` + "`" + `
type Data struct {
Name string
Items []string
Admin bool
}
// Парсинг и выполнение:
t := template.Must(template.New("page").Parse(tmpl))
data := Data{
Name: "Alice",
Items: []string{"Go", "Docker", "Kubernetes"},
Admin: true,
}
// В os.Stdout:
t.Execute(os.Stdout, data)
// В http.ResponseWriter:
func handler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, data)
}`)
utils.Println(utils.ColorBold + "Экранирование и безопасность:" + utils.ColorReset)
utils.Code(`// html/template автоматически экранирует пользовательский ввод:
// <script>alert(1)</script> → &lt;script&gt;alert(1)&lt;/script&gt;
// Если нужно вставить безопасный HTML — использовать template.HTML:
type Page struct {
Title string
Content template.HTML // не экранируется
}
// Никогда не делай:
// template.HTML(userInput) — XSS!
// text/template — без экранирования (для не-HTML):
import "text/template"
// Используй для генерации кода, конфигов, emails в plain text`)
utils.Println(utils.ColorBold + "Шаблонные функции и Actions:" + utils.ColorReset)
utils.Code(`// Функции в шаблоне:
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"add": func(a, b int) int { return a + b },
}
t := template.New("t").Funcs(funcMap).Parse(` + "`" + `
{{.Name | upper}}
{{add 2 3}}
` + "`" + `)
// Actions (действия):
{{/* комментарий */}}
{{.Field}} // вывести поле
{{.Method arg}} // вызов метода
{{if .Cond}} ... {{end}}
{{if .Cond}} ... {{else}} ... {{end}}
{{range .Slice}} {{.}} {{end}}
{{range .Map}} {{.}} {{end}}
{{with .Val}} {{.}} {{end}} // установить . = Val если не ноль
{{template "name" .}} // вложенный шаблон
// Шаблоны из файлов:
t := template.Must(template.ParseFiles("index.html", "base.html"))
t := template.Must(template.ParseGlob("templates/*.html"))`)
utils.Println(utils.ColorBold + "Парсинг HTML (html пакет):" + utils.ColorReset)
utils.Code(`import "html"
// Экранирование и разэкранирование HTML-сущностей:
escaped := html.EscapeString("<script>alert('xss')</script>")
// &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;
unescaped := html.UnescapeString("&lt;b&gt;жирный&lt;/b&gt;")
// <b>жирный</b>
// Для разбора HTML-дерева — golang.org/x/net/html:
import "golang.org/x/net/html"
resp, _ := http.Get("https://example.com")
doc, _ := html.Parse(resp.Body)
// Рекурсивный обход:
var walk func(*html.Node)
walk = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, attr := range n.Attr {
if attr.Key == "href" {
fmt.Println(attr.Val)
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
walk(c)
}
}
walk(doc)`)
utils.Footer()
}