Интерфейс Stringer
В прошлом уроке интерфейс был объявлением о вакансии. Своё объявление есть и у пакета fmt: «умеет рассказать о себе строкой». Если тип значения это умеет, fmt печатает значение так, как тип сам о себе расскажет.
Начнём с типа, который пока этого не умеет. Объявление type Celsius float64 создаёт новый тип для температуры. Его базовый тип — float64: значения и арифметика у них общие. Но fmt.Println печатает температуру голым числом, без единиц:
package main
import "fmt"
type Celsius float64
func main() {
t := Celsius(21.5)
fmt.Println(t, t+1) // 21.5 22.5
}Глядя на 21.5, не скажешь, градусы это, метры или рубли. Хочется, чтобы программа печатала 21.5°C. Методы можно объявить для Celsius, а для float64 — нет: этот тип объявлен не в вашем пакете. Именно метод с особым именем меняет то, как fmt печатает температуру.
Интерфейс fmt.Stringer
Пакет fmt объявляет интерфейс Stringer с одним методом String() string. Перед печатью значения fmt.Println действует так:
- Проверяет, реализует ли тип значения интерфейс
Stringer. - Если реализует, печатает строку, которую вернул
String, а не само значение. - Если метода нет, печатает значение по правилам его базового типа.
Так же поступают fmt.Print, fmt.Printf со знаком %v и другие функции пакета.
Тип реализует Stringer неявно, как любой другой интерфейс. Достаточно метода, у которого:
- имя
String; - нет параметров;
- результат типа
string.
Метод String
Метод String для Celsius собирает строку из числа и знака градуса:
func (c Celsius) String() string {
return fmt.Sprintf("%v°C", float64(c))
}Разберём тело метода:
float64(c)превращает температуру в обычное число;%vподставляет это число в шаблон;- за числом идут знаки
°C.
Раздел о частой ошибке покажет, зачем здесь преобразование.
Строка без печати
Метод String должен вернуть строку, а не напечатать её. Для этого в fmt есть функции с буквой S в начале имени. Разберём две из них.
fmt.Sprintf
Функция fmt.Sprintf работает как fmt.Printf, но ничего не печатает: готовый текст она возвращает строкой. Шаблон у неё тот же, что у fmt.Printf: %v подставляет значение, а %d — целое число. Её берут, когда значение нужно вставить в середину текста.
fmt.Sprint
Так же fmt.Sprint повторяет fmt.Print: шаблона у неё нет, а аргументы она превращает в строку по правилам fmt.Print. Запись fmt.Sprint(float64(c)) + "°C" даёт ту же строку, что и шаблон "%v°C".
Печать температуры
Теперь fmt печатает температуру с единицами:
t := Celsius(21.5)
fmt.Println(t, t+1) // 21.5°C 22.5°C
fmt.Println(float64(t)) // 21.5
s := fmt.Sprint(float64(t)) + " degrees"
fmt.Println(s) // 21.5 degreesДавайте разберём пример по строкам:
t := Celsius(21.5)создаёт температуру.- Первый
Printlnпечатает21.5°C 22.5°C: суммаt+1сохраняет типCelsiusи тоже печатается черезString. - Преобразование
float64(t)во второмPrintlnдаёт обычное число типаfloat64. МетодаStringу этого типа нет, поэтомуfmtпечатает его как число:21.5. - Строка с
fmt.Sprintсобирает текст21.5 degrees. - Последний
Printlnпечатает эту строку.
Здесь картинку с вакансией нужно уточнить. Умение принадлежит не самому числу, а его типу. Преобразование меняет тип значения, и вместе с типом пропадают его методы: число то же, а рассказать о себе оно уже не может.
Частая ошибка
Внутри String значение передали в fmt.Sprint без преобразования типа:
func (c Celsius) String() string {
return fmt.Sprint(c) + "°C" // ✗
}fatal error: stack overflowПрограмма компилируется, но падает при первой печати температуры. fmt.Sprint(c) видит, что c реализует Stringer, и вызывает c.String(), а тот снова вызывает fmt.Sprint(c). Рекурсия не кончается, пока не переполнится стек. Внутри String передавайте в fmt значение базового типа: у float64(c) метода String нет, и круг разрывается.
Главное
- Тип с методом
String() stringреализуетfmt.Stringer, иfmtпечатает его значения через этот метод. fmt.Sprintfиfmt.Sprintне печатают, а возвращают готовую строку.- Преобразование
float64(t)убирает методы типа, иfmtпечатает голое число. - Внутри
Stringпередавайте вfmtзначение базового типа, иначе рекурсия переполнит стек.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.