Язык Go — пакет fmt (часть 1)
Форматированный вывод в Go — глаголы форматирования, флаги, ширина и точность, явные индексы аргументов и обработка ошибок формата.
Содержание
Пакет fmt реализует форматированный ввод-вывод — по аналогии с printf и scanf из C. Глаголы форматирования похожи на Си-шные, но проще.
Семейства функций вывода
Функции вывода делятся на четыре семейства в зависимости от назначения:
Print,Println,Printf— пишут вos.StdoutSprint,Sprintln,Sprintf— возвращают строкуFprint,Fprintln,Fprintf— пишут вio.WriterAppend,Appendln,Appendf— добавляют результат к срезу байт
Поведение внутри каждого семейства определяется суффиксом имени:
Print/Sprint/Fprint/Append— используют формат по умолчанию для каждого аргумента; добавляют пробел между операндами, если ни один из них не является строкойPrintln/Sprintln/Fprintln/Appendln— всегда добавляют пробелы и переносят строку в концеPrintf/Sprintf/Fprintf/Appendf— используют строку формата с глаголами
Глаголы форматирования
Общие
%v значение в формате по умолчанию
для структур флаг + (%+v) добавляет имена полей
%#v представление значения в синтаксисе Go
(бесконечности и NaN для float печатаются как ±Inf и NaN)
%T тип значения в синтаксисе Go
%% знак процента; не потребляет аргумент
Булевы значения
%t слово true или false
Целые числа
%b двоичная система (основание 2)
%c символ Unicode с указанным кодом
%d десятичная система (основание 10)
%o восьмеричная система (основание 8)
%O восьмеричная система с префиксом 0o
%q символьный литерал в одинарных кавычках, безопасно экранированный по правилам Go
%x шестнадцатеричная, строчные буквы a-f
%X шестнадцатеричная, заглавные буквы A-F
%U формат Unicode: U+1234; то же что "U+%04X"
Числа с плавающей точкой и комплексные числа
%b научная нотация без десятичной точки, экспонента — степень двойки
(как strconv.FormatFloat с форматом 'b'), например -123456p-78
%e научная нотация, например -1.234456e+78
%E научная нотация, например -1.234456E+78
%f десятичная точка без экспоненты, например 123.456
%F синоним %f
%g %e для больших экспонент, %f для остальных; точность — см. ниже
%G %E для больших экспонент, %F для остальных
%x шестнадцатеричная нотация (экспонента степени двойки), например -0x1.23abcp+20
%X шестнадцатеричная нотация в верхнем регистре, например -0X1.23ABCP+20
Экспонента всегда в десятичной форме. Для всех форматов кроме %b экспонента содержит минимум две цифры.
Строки и срезы байт
%s необработанные байты строки или среза
%q строка в двойных кавычках, безопасно экранированная по правилам Go
%x шестнадцатеричная, строчные буквы, два символа на байт
%X шестнадцатеричная, заглавные буквы, два символа на байт
Срезы
%p адрес нулевого элемента в шестнадцатеричной нотации с префиксом 0x
Указатели
%p шестнадцатеричная нотация с префиксом 0x
Глаголы %b, %d, %o, %x и %X также работают с указателями — форматируют их значение как целое число.
Формат по умолчанию для %v
bool: %t
int, int8 и т.д.: %d
uint, uint8 и т.д.: %d, %#x при использовании с %#v
float32, complex64 и т.д.:%g
string: %s
chan: %p
pointer: %p
Для составных типов элементы форматируются рекурсивно по тем же правилам:
struct: {поле0 поле1 ...}
array, slice: [элем0 элем1 ...]
map: map[ключ1:знач1 ключ2:знач2 ...]
указатель на выше: &{}, &[], &map[]
Ширина и точность
Ширина задаётся необязательным целым числом непосредственно перед глаголом. Если не указана — используется минимально необходимый размер. Точность задаётся после ширины через точку. Точка без числа задаёт точность 0. Примеры:
%f ширина и точность по умолчанию
%9f ширина 9, точность по умолчанию
%.2f ширина по умолчанию, точность 2
%9.2f ширина 9, точность 2
%9.f ширина 9, точность 0
Ширина и точность измеряются в рунах (кодовых точках Unicode) — в отличие от C, где единицей измерения всегда являются байты. Вместо числа можно использовать * — тогда значение берётся из следующего аргумента типа int.
Для большинства значений ширина — это минимальное количество рун в выводе; при необходимости добавляются пробелы.
Для строк, срезов и массивов байт точность ограничивает длину входных данных (не вывода), обрезая при необходимости. При форматировании с %x или %X точность измеряется в байтах.
Для чисел с плавающей точкой:
- ширина задаёт минимальную ширину поля
- точность — количество знаков после запятой (кроме
%g/%G, где точность — максимальное количество значащих цифр, хвостовые нули убираются) - точность по умолчанию для
%e,%fи%#g— 6; для%g— минимальное количество цифр, однозначно идентифицирующих значение
fmt.Printf("%6.3f", 12.345) // " 12.345"
fmt.Printf("%.3g", 12.345) // "12.3"
Для комплексных чисел ширина и точность применяются к каждой составляющей отдельно, результат берётся в скобки:
fmt.Printf("%f", 1.2+3.4i) // "(1.200000+3.400000i)"
Флаги
'+' всегда печатать знак для числовых значений;
гарантировать только ASCII-символы для %q (%+q)
'-' выравнивание по левому краю (пробелы справа, а не слева)
'#' альтернативный формат:
добавить 0b для двоичных (%#b), 0 для восьмеричных (%#o),
0x или 0X для шестнадцатеричных (%#x или %#X);
убрать 0x для %p (%#p);
для %q — вывести сырую строку в обратных кавычках, если
strconv.CanBackquote возвращает true;
всегда печатать десятичную точку для %e, %E, %f, %F, %g, %G;
не убирать хвостовые нули для %g и %G;
для %U — добавить символ если он печатаемый: U+0078 'x' (%#U)
' ' (пробел) оставить место для знака в числах (% d);
добавлять пробелы между байтами при выводе строк и срезов
в шестнадцатеричном формате (% x, % X)
'0' дополнять ведущими нулями вместо пробелов;
для чисел — ведущие нули идут после знака
Флаги, не предусмотренные для конкретного глагола, игнорируются. Например, для десятичных чисел нет альтернативного формата — %#d и %d работают одинаково.
Интерфейсы и форматирование
Если операнд является значением интерфейса — используется его конкретное внутреннее значение, а не сам интерфейс:
var i interface{} = 23
fmt.Printf("%v\n", i) // выведет: 23
За исключением глаголов %T и %p, для операндов реализующих определённые интерфейсы применяется специальное форматирование (в порядке приоритета):
- Если операнд является
reflect.Value— он заменяется конкретным хранимым значением - Если операнд реализует интерфейс
Formatter— вызывается его метод; интерпретация глаголов и флагов определяется реализацией - Если используется
%#vи операнд реализуетGoStringer— вызывается его метод
Если формат допустим для строки (%s, %q, %x, %X), или это %v без #, применяются ещё два правила:
- Если операнд реализует интерфейс
error— вызывается методError()для получения строки - Если операнд реализует метод
String() string— вызывается он
Для составных операндов (срезов, структур) формат применяется к каждому элементу рекурсивно. Так, %q возьмёт каждый элемент среза строк в кавычки, а %6.2f применится к каждому элементу массива чисел с плавающей точкой.
Исключение: срез байт с строковым глаголом (%s, %q, %x, %X) обрабатывается как единое целое — как строка.
Осторожно: бесконечная рекурсия
Если тип реализует String() и внутри вызывает Sprintf с тем же значением — получится бесконечная рекурсия:
// опасно: бесконечная рекурсия
type X string
func (x X) String() string { return fmt.Sprintf("<%s>", x) }
// правильно: явное преобразование в string перед рекурсивным вызовом
func (x X) String() string { return fmt.Sprintf("<%s>", string(x)) }
Аналогичная проблема может возникнуть с самореферентными структурами данных — например, срезом, содержащим себя как элемент.
Также стоит помнить: fmt не вызывает методы форматирования (Error, String) для неэкспортируемых полей структур.
Явные индексы аргументов
По умолчанию каждый глагол форматирования потребляет следующий по порядку аргумент. Но нотация [n] перед глаголом позволяет обратиться к n-му аргументу (с индексацией с 1). После обработки [n] последующие глаголы продолжают с аргумента n+1.
fmt.Sprintf("%[2]d %[1]d\n", 11, 22)
// результат: "22 11"
Та же нотация работает для ширины и точности через *:
fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6)
// эквивалентно:
fmt.Sprintf("%6.2f", 12.0)
// результат: " 12.00"
Сброс индекса позволяет вывести одно и то же значение несколько раз:
fmt.Sprintf("%d %d %#[1]x %#x", 16, 17)
// результат: "16 17 0x10 0x11"
Ошибки формата
Если аргумент не соответствует глаголу — в строку вывода вставляется описание проблемы:
Неверный тип или неизвестный глагол: %!verb(type=value)
Printf("%d", "hi"): %!d(string=hi)
Слишком много аргументов: %!(EXTRA type=value)
Printf("hi", "guys"): hi%!(EXTRA string=guys)
Слишком мало аргументов: %!verb(MISSING)
Printf("hi%d"): hi%!d(MISSING)
Не целое число для ширины/точности: %!(BADWIDTH) или %!(BADPREC)
Printf("%*s", 4.5, "hi"): %!(BADWIDTH)hi
Printf("%.*s", 4.5, "hi"): %!(BADPREC)hi
Неверный индекс аргумента: %!(BADINDEX)
Printf("%*[2]d", 7): %!d(BADINDEX)
Printf("%.[2]d", 7): %!d(BADINDEX)
Все сообщения об ошибках начинаются с %!, иногда за которым следует один символ (глагол), и заканчиваются описанием в скобках.
Если метод Error или String вызывает панику при обращении из функции вывода — fmt перехватывает её и форматирует сообщение:
// если String() вызывает panic("bad")
// в выводе появится:
// %!s(PANIC=bad)
Исключение: паника вызвана nil-получателем в методах Error, String или GoString — в этом случае выводится просто <nil>.
Итоги
- Четыре семейства функций:
Print*→ stdout,Sprint*→ строка,Fprint*→io.Writer,Append*→ срез байт - Суффикс определяет поведение: без суффикса — формат по умолчанию,
ln— всегда пробелы и перенос,f— строка формата с глаголами - Основные глаголы:
%v(по умолчанию),%d(целые),%s(строки),%f(float),%T(тип),%p(указатель) %+vдобавляет имена полей структуры,%#vвыводит синтаксис Go- Ширина и точность измеряются в рунах, а не байтах (в отличие от C)
[n]позволяет явно указать индекс аргумента; позволяет использовать один аргумент несколько раз- Ошибки формата не вызывают панику — вместо этого в строку вставляется описание проблемы вида
%!verb(...) - Методы
String()иError()вызываются автоматически при форматировании, но нужно следить за бесконечной рекурсией
Следующий шаг: Пакет fmt (часть 2) — функции сканирования (Scan, Scanf, Sscan) и чтение форматированного ввода.