Go: с нуля до своего блога Урок 11 из 50
Структура в Go: свой тип вместо пяти переменных
Одиннадцатый урок курса по Go. Структура — свой тип: type Article struct вместо трёх словарей, которые держали в согласии вручную. Поля и точка, литерал с именами и без, нулевая структура, срез структур и главная ловушка: структура передаётся копией, и правка внутри range пропадает.
Зачем это нужно
В прошлых уроках блог держал статьи так: срез адресов, словарь заголовков, словарь объёмов. Добавляете статью — надо не забыть про три места. Забыли одно, и заголовок есть, а времени чтения нет.
Так жить нельзя, и Go этого не требует. У статьи есть заголовок, объём и язык — значит, нужен тип «статья», в котором всё это лежит вместе.
Образ. Картотека. Раньше в одном ящике лежали заголовки, в другом — объёмы, в третьем — языки, и вы держали их в согласии руками. Структура — это одна карточка, где всё про статью написано разом.
Сразу целиком
Новая папка, go mod init sabaq10, main.go:
package main
import "fmt"
type Article struct {
Title string
Words int
Lang string
}
func readingTime(a Article) int {
return (a.Words + 199) / 200
}
func main() {
first := Article{
Title: "Что такое шанырак",
Words: 400,
Lang: "kz",
}
second := Article{"О степи", 150, "kz"}
var draft Article
fmt.Println(first.Title, "·", readingTime(first), "мин")
fmt.Println(second.Title, "·", readingTime(second), "мин")
fmt.Printf("черновик: %q %d %q\n", draft.Title, draft.Words, draft.Lang)
blog := []Article{first, second}
blog = append(blog, Article{Title: "Привет, мир", Words: 1000, Lang: "kz"})
for i, a := range blog {
fmt.Printf("%d. %s — %d мин\n", i+1, a.Title, readingTime(a))
}
fmt.Println(first)
}
Прежде чем запускать — не заглядывая ниже, скажите вслух, что она напечатает. Потом запустите и сверьте.
Что такое шанырак · 2 мин
О степи · 1 мин
черновик: "" 0 ""
1. Что такое шанырак — 2 мин
2. О степи — 1 мин
3. Привет, мир — 5 мин
{Что такое шанырак 400 kz}
Разбор
type ... struct — вы объявляете свой тип
type Article struct {
Title string
Words int
Lang string
}
type говорит: дальше идёт новый тип. Article — его имя. struct — из чего он сделан: из перечисленных полей, у каждого своё имя и свой тип.
С этой минуты Article — такой же тип, как int или string. Его можно объявлять, передавать в функции, класть в срез и в словарь.
Имена полей начинаются с заглавной буквы не случайно, но зачем — станет важно в уроке про пакеты. Пока просто пишите так.
Поля и точка
first.Title // прочитать
first.Words = 420 // записать
Точка достаёт поле из значения. Никаких скобок и ключей: поля известны заранее, и опечатку в имени компилятор поймает сразу, чего словарь никогда не сделает.
Вот и первая выгода структуры перед словарём. views["tittle"] вернёт ноль и промолчит. first.Tittle не соберётся.
Литерал: с именами полей и без
Article{Title: "Что такое шанырак", Words: 400, Lang: "kz"}
Article{"О степи", 150, "kz"}
Обе записи создают одно и то же. Первая называет поля, вторая полагается на их порядок в объявлении.
Пишите первую. Вторая короче ровно до того дня, когда в тип добавят четвёртое поле или поменяют местами два — и весь код с такими литералами придётся переписывать, а часть его соберётся молча и неправильно.
Нулевая структура
var draft Article
Напечаталось "" 0 "". Структура готова к работе сразу: каждое поле получило нулевое значение своего типа — пустую строку, ноль, false. Ничего создавать не нужно, никакого make.
Образ. Пустой бланк. Графы не заполнены, но бланк существует, лежит на столе, и в него можно писать.
Это удобнее, чем кажется. Пустая структура — законный черновик статьи, а не ошибка.
Срез структур — то, ради чего всё затевалось
blog := []Article{first, second}
blog = append(blog, Article{Title: "Привет, мир", Words: 1000, Lang: "kz"})
Один срез вместо трёх словарей. Добавляется статья одной строкой, и потерять половину её полей теперь невозможно: они переехали вместе.
Ловушка: структура передаётся копией
Вот то, ради чего стоит дочитать урок.
for _, a := range blog {
a.Title = "ИЗМЕНЕНО"
}
fmt.Println(blog[0].Title) // Что такое шанырак
Ничего не изменилось. range кладёт в a копию элемента, и вы правите копию, которая тут же выбрасывается. Компилятор молчит: с его точки зрения код законный.
То же самое с функцией: readingTime(a) получает копию, и, если бы она что-то поменяла внутри, снаружи это не увидели бы.
Образ. Ксерокопия. Вам дали копию документа — пишите на ней сколько угодно, в подшивке останется прежнее.
Как изменить по-настоящему — через индекс:
blog[0].Title = "ИЗМЕНЕНО" // вот это работает
Сравните с уроком про срез: там кусок среза смотрел в ту же память, и правка была видна. Здесь другое: сам срез по-прежнему один, но range выдаёт вам копию элемента, а не сам элемент. Настоящее решение — указатели, и это следующий урок.
fmt.Println печатает структуру целиком
fmt.Println(first) напечатал {Что такое шанырак 400 kz} — значения полей в фигурных скобках. Для отладки удобно, а с %+v будут ещё и имена полей: fmt.Printf("%+v\n", first).
Карта урока
Скажите своими словами
Не подглядывая, ответьте вслух или на бумаге. Ответы — в конце урока.
- Чем
first.Tittleлучше, чемviews["tittle"], если в обоих случаях опечатка? var draft Article— что лежит в полях и надо ли что-то создавать?- Вы прошли
rangeпо срезу структур и записали в поле. Почему в срезе ничего не изменилось?
Задание
Обязательное. Объявите тип Article с полями Title, Words и Lang. Соберите срез из четырёх статей и напишите функцию longestRead(blog []Article) Article, которая возвращает статью с самым долгим временем чтения. Напечатайте её заголовок и время.
По желанию.
- Функция
byLang(blog []Article, lang string) []Article— только статьи на нужном языке. - Добавьте в тип поле
Published boolи посчитайте, сколько статей опубликовано. - Повторите ловушку намеренно: измените поле внутри
range, убедитесь, что ничего не произошло, потом сделайте то же через индекс.
Куда это встанет в блоге
Отсюда и до конца курса статья будет структурой. В уроке про шаблоны вы отдадите []Article в HTML, и он развернётся в карточки. В уроке про базу строка таблицы превратится в Article одной командой. А три словаря, которые мы держали в согласии руками, исчезнут — step-5 в репозитории курса ровно об этом.
Ответы
Показать ответы
- Тем, что
first.Tittleне соберётся: поля типа известны компилятору, и он поймает опечатку до запуска. Словарь принимает любой ключ, поэтомуviews["tittle"]тихо вернёт ноль, и ошибку вы найдёте по неверным числам, а не по сообщению. - В полях нулевые значения их типов: пустая строка, ноль,
false. Создавать ничего не нужно — в отличие от словаря, структура готова к работе сразу после объявления. - Потому что
rangeкладёт в переменную копию элемента. Вы изменили копию, а она исчезла на следующем витке. Изменить сам элемент можно через индекс —blog[i].Title = …, — а по-настоящему эту разницу закроет следующий урок, про указатели.
Источники
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.