· 15 мин 👁 1.1k Начинающий

Язык Go — пакет fmt (часть 1)

Форматированный вывод в Go — глаголы форматирования, флаги, ширина и точность, явные индексы аргументов и обработка ошибок формата.

fmtформатированиеprintfsprintfвыводглаголы
Содержание

Пакет fmt реализует форматированный ввод-вывод — по аналогии с printf и scanf из C. Глаголы форматирования похожи на Си-шные, но проще.

Семейства функций вывода

Функции вывода делятся на четыре семейства в зависимости от назначения:

  • Print, Println, Printf — пишут в os.Stdout
  • Sprint, Sprintln, Sprintf — возвращают строку
  • Fprint, Fprintln, Fprintf — пишут в io.Writer
  • Append, 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, для операндов реализующих определённые интерфейсы применяется специальное форматирование (в порядке приоритета):

  1. Если операнд является reflect.Value — он заменяется конкретным хранимым значением
  2. Если операнд реализует интерфейс Formatter — вызывается его метод; интерпретация глаголов и флагов определяется реализацией
  3. Если используется %#v и операнд реализует GoStringer — вызывается его метод

Если формат допустим для строки (%s, %q, %x, %X), или это %v без #, применяются ещё два правила:

  1. Если операнд реализует интерфейс error — вызывается метод Error() для получения строки
  2. Если операнд реализует метод 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) и чтение форматированного ввода.