JavaRush /Курси /Go SELF /fmt.Printf: %v, %T, %d, %s, \n для виводу та діагностики

fmt.Printf: %v, %T, %d, %s, \n для виводу та діагностики

Go SELF
Рівень 6 , Лекція 4
Відкрита

1. Навіщо нам fmt.Printf, якщо є fmt.Println

Коли ви лише починаєте програмувати, fmt.Println здається ідеальним: він друкує значення, сам розставляє пробіли та автоматично додає переведення рядка. Але саме тоді, коли ви намагаєтеся зрозуміти, чому щось працює не так, Println стає надто «добрим» і трохи приховує деталі. fmt.Printf — це режим, у якому ви самі керуєте виводом: де будуть пробіли, де переведення рядка, як саме друкувати значення і який у них тип.

Уявіть, що Println — це кафе, де вам приносять «комбо № 1», а Printf — це конструктор: ви самі обираєте булку, котлету, соус і навіть те, як саме буде оформлено ваше замовлення. Іноді «комбо» достатньо. Але для налагодження й акуратного виводу частіше потрібен саме конструктор.

Невелике порівняння:

Функція Що робить Переведення рядка Коли зручна
fmt.Print
Друкує без форматування Ні Короткі фрагменти тексту без прикрас
fmt.Println
Друкує та додає пробіли між аргументами Так Швидкий вивід, щоб просто побачити результат
fmt.Printf
Друкує за шаблоном, тобто за форматним рядком Ні, зазвичай потрібен \n Акуратний вивід і діагностика типів та значень

2. Форматний рядок і специфікатори

Щоб fmt.Printf був справді корисним, потрібно зрозуміти просту річ: у Printf є форматний рядок — це звичайний текст, усередині якого є спеціальні «віконця» для підстановки значень. Ці «віконця» називаються специфікаторами (або format verbs) і записуються як %....

Важливий технічний факт, який зараз корисно тримати в голові: сигнатура fmt.Printf виглядає так — першим аргументом іде рядок формату, а далі — будь-яка кількість значень.
Тобто схема проста: спочатку шаблон, потім дані.

Зручно уявляти роботу Printf ось так:

flowchart LR
    A["Форматний рядок: 'x=%d type=%T '"] --> C["fmt.Printf"]
    B["Аргументи: x, x"] --> C["fmt.Printf"]
    C --> D["стандартний вивід: x=10 type=int"]

І тепер найважливіше правило: кількість специфікаторів у форматному рядку має збігатися з кількістю переданих значень. Інакше ви отримаєте або %!(EXTRA …) у виводі, або %!(MISSING) — і це виглядатиме як прокляття древніх богів.

3. Основні специфікатори та переведення рядка

%v: універсальний «покажи значення якось нормально»

%v — це специфікатор, який рятує, коли ви не хочете думати, який саме тип друкуєте. Він означає value, тобто значення, і друкує його у стандартному представленні.

Коли ви налагоджуєте програму, найчастіше вам потрібно просто побачити, що зараз лежить у змінній. Не красиво, не в таблицю, не у звіт, а просто: «що там?». Ось тут %v — ваш найкращий помічник.

Мініприклад:

package main

import "fmt"

func main() {
    x := 10
    fmt.Printf("x=%v\n", x) // x=10
}

Зауважте: Printf сам переведення рядка не робить. Тому \n наприкінці — це не для краси, а спосіб не дати наступному виводу «прилипнути» до цього.

Ще один приклад із кількома типами:

package main

import "fmt"

func main() {
    name := "Гофер"
    ok := true

    fmt.Printf("name=%v ok=%v\n", name, ok) // name=Гофер ok=true
}

Тут %v зручний тим, що однаково спокійно працює і з рядками, і з булевими значеннями, і з числами. Для діагностики — ідеально.

%T: друкуємо тип і перестаємо гадати

%T друкує тип значення, яке ви передали в Printf. Це дуже корисно, коли ви використовуєте := і покладаєтеся на виведення типу (type inference), а потім дивуєтеся, чому вираз не компілюється або чому порівняння «не те».

У документації та матеріалах із Go часто показують комбінацію %v + %T саме як спосіб одночасно побачити значення та його тип.
Це буквально рентген для змінних.

Приклад «значення + тип»:

package main

import "fmt"

func main() {
    x := 10
    fmt.Printf("x=%v type=%T\n", x, x) // x=10 type=int
}

Сенс дуже практичний: ви починаєте мислити як компілятор Go. Не «ну це ж число», а «це int». І поступово зникає магічне мислення.

Мінідетектив: чому тип узагалі може виявитися неочікуваним

Іноді тип дивує через літерали та типи за замовчуванням. Наприклад, рядковий літерал зазвичай має тип string, цілочисельний — int, дробовий — зазвичай float64. У матеріалах із Go часто пояснюють: коли неявно типізовані (untyped) значення передають у Printf, вони отримують «тип за замовчуванням». Саме тому %T так корисний.

У тему констант сьогодні не заглиблюватимемося, але як практичний прийом це дуже зручно: якщо сумніваєтеся — друкуйте %T.

%d і %s: строгий режим для акуратного виводу

%v — універсальний. Але якщо ви друкуєте дані для користувача або хоча б для себе через тиждень, іноді корисно ввімкнути «строгий режим»: друкувати числа як числа, рядки як рядки. Так вивід стає акуратнішим і допомагає швидше знаходити помилки.

  • %d — для цілих чисел (зазвичай int).
  • %s — для рядків (string).

Приклад: друк простого виразу «як у підручнику»:

package main

import "fmt"

func main() {
    a := 2
    b := 3

    fmt.Printf("%d + %d = %d\n", a, b, a+b) // 2 + 3 = 5
}

Приклад: рядковий шаблон для вітання:

package main

import "fmt"

func main() {
    name := "Анна"
    fmt.Printf("Привіт, %s!\n", name) // Привіт, Анна!
}

Такий вивід одразу виглядає як програма, а не як набір налагоджувальних роздруківок.

\n: переведення рядка — це ваша відповідальність

У fmt.Println переведення рядка додається автоматично. У fmt.Printf — ні. Це не баг і не «чому Go такий строгий», а просто принцип: якщо ви керуєте форматом, то керуєте всім, у тому числі переведенням рядка.

Тому, якщо ви хочете виводити рядки окремо, найчастіше наприкінці форматного рядка пишуть \n.

Поганий приклад (вивід злипнеться):

package main

import "fmt"

func main() {
    fmt.Printf("first")
    fmt.Printf("second")
    fmt.Printf("third")
    // firstsecondthird
}

Нормальний приклад:

package main

import "fmt"

func main() {
    fmt.Printf("first\n")  // first
    fmt.Printf("second\n") // second
    fmt.Printf("third\n")  // third
}

Відтепер вважайте, що переведення рядка вже під вашим контролем.

4. Приклад: TripBudget

Давайте розвивати простий навчальний застосунок TripBudget — калькулятор бюджету поїздки. Ми поки не використовуємо масиви, зрізи, функції та не будуємо складну архітектуру. Ми просто читаємо введення, рахуємо й акуратно друкуємо результат. На цьому рівні нам важливіше навчитися бачити типи й значення, ніж будувати систему.

Версія 0: швидко працює

Уявімо, що в нас є такі змінні: місто, кількість днів і бюджет на день (у цілих доларах). Ми хочемо порахувати загальну суму.

package main

import "fmt"

func main() {
    city := "Берлін"
    days := 4
    perDay := 70

    total := days * perDay
    fmt.Println(city, days, perDay, total) // Берлін 4 70 280
}

Це працює, але вивід надто сирий. Користувачеві незрозуміло, що з цього є що.

Версія 1: вивід для людей

package main

import "fmt"

func main() {
    city := "Берлін"
    days := 4
    perDay := 70

    total := days * perDay
    fmt.Printf("Поїздка до %s: %d днів, $%d на день, усього = $%d\n", city, days, perDay, total)
    // Поїздка до Берліна: 4 днів, $70 на день, усього = $280
}

Тепер вивід читається як нормальне повідомлення, а не як «дамп пам’яті».

Версія 2: діагностичний режим

Іноді ви змінюєте код, переносите змінні, додаєте нові — і раптом починаєте сумніватися: «а days точно int?», «а city точно string?». Ось тут ми додаємо %T.

package main

import "fmt"

func main() {
    city := "Берлін"
    days := 4
    perDay := 70

    fmt.Printf("city=%v type=%T\n", city, city)       // city=Берлін type=string
    fmt.Printf("days=%v type=%T\n", days, days)       // days=4 type=int
    fmt.Printf("perDay=%v type=%T\n", perDay, perDay) // perDay=70 type=int
}

Це особливо корисно, коли ви активно використовуєте :=, тому що тип виводиться автоматично, і часом ви самі собі влаштовуєте сюрприз.

5. Патерни Printf, які економлять час

Зараз буде кілька патернів, які варто запам’ятати. Я навмисно роблю їх короткими, щоб вони легше осідали в пам’яті.

Показати все в одному рядку

Коли ви налагоджуєте умову або обчислення, зручно вивести все одразу:

package main

import "fmt"

func main() {
    a := 10
    b := 3
    fmt.Printf("a=%d b=%d a/b=%d a%%b=%d\n", a, b, a/b, a%b)
    // a=10 b=3 a/b=3 a%b=1
}

Зверніть увагу на a%%b: щоб надрукувати символ % у форматному рядку, ви пишете %%. Це не нова тема «форматування», а просто маленька побутова деталь: % у рядку формату — службовий символ.

Перевірити, що вираз справді bool

Дуже корисно друкувати не лише числа, а й логічні вирази:

package main

import "fmt"

func main() {
    age := 17
    canEnter := age >= 18

    fmt.Printf("age=%d canEnter=%v type(canEnter)=%T\n", age, canEnter, canEnter)
    // age=17 canEnter=false type(canEnter)=bool
}

Це допомагає перестати плутати значення й умову. У Go умова обов’язково має бути bool, і %T добре закріплює це на практиці.

Передали не те — побачили одразу

Іноді ви випадково друкуєте змінну не тим специфікатором. Наприклад, рядок — %d. Тоді у виводі ви одразу побачите щось дивне. Це хороший сигнал: «я друкую не те, чим це здається».

Хороша звичка: якщо вивід виглядає дивно, додайте поруч %T і подивіться, що ви насправді друкуєте.

6. Типові помилки під час роботи з fmt.Printf

Помилка № 1: забули \n і отримали «злиплий» вивід.
Це класика: ви друкуєте кілька рядків підряд, очікуєте акуратні повідомлення, а вони перетворюються на одну довгу «ковбасу». Причина проста: Printf не додає переведення рядка автоматично. Якщо ви хочете виводити рядки окремо, \n — ваш обов’язок.

Помилка № 2: не збіглася кількість специфікаторів і кількість аргументів.
Коли у форматному рядку три «віконця» (%d %d %d), а ви передали два значення або чотири, вивід починає нагадувати повідомлення інопланетян. У цей момент не потрібно панікувати: просто перечитайте форматний рядок і порахуйте, скільки підстановок ви насправді зробили.

Помилка № 3: переплутали %d і %s.
Ця помилка особливо неприємна новачкам, бо «ну воно ж виглядає як текст або число». Але Go — мова, яка поважає типи. %d — для цілих чисел, %s — для рядків. Якщо ви сумніваєтеся, який у вас тип, — додайте %T і перестаньте гадати.

Помилка № 4: використовують %v всюди й втрачають читабельність.
%v чудово підходить для діагностики, але якщо ви робите «людський» вивід, %d і %s часто читаються краще: вони дисциплінують формат і вас самого. Хороший баланс такий: у налагодженні — %v/%T, у користувацькому повідомленні — точніші %d/%s.

Помилка № 5: намагаються зробити Printf «як Println» і дивуються пробілам.
Println сам ставить пробіли між аргументами. Printf цього не робить: пробіли, коми, двокрапки та інша пунктуація — частина форматного рядка. Тобто якщо ви не написали пробіл, його не буде. Спочатку це дратує, а потім перетворюється на суперсилу: ви починаєте друкувати рівно так, як задумали.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ