День 03 · Типы и арифметика · страница 4 из 6

Строки и fmt.Printf

Чек, накладная, сообщение об остатке — текст с числами внутри. Println для этого неудобен: ставит пробелы, где не просят, и не умеет «05 коп.». Сегодня — fmt.Printf, печать по шаблону, и go vet, который находит ошибки шаблонов до запуска.


Склейка строк

Оператор + у строк не складывает, а склеивает.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func main() {
	name := "Север"
	title := "Склад " + name
	fmt.Println(title)
	fmt.Println("12"+"5", 12+5)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
Склад Север
125 17

Как это читать. Пробел между «Склад» и «Север» — внутри первой строки: + своих пробелов не добавляет. "12"+"5" — склейка строк, 125; 12+5 — сумма чисел, 17. Оператор один, действие разное — решает тип. А строку с числом сложить нельзя:

Код для чтения · разбираем, набирать не нужно
	n := 5
	fmt.Println("Итого: " + n)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:7:14: invalid operation: "Итого: " + n (mismatched types untyped string and int)

С двумя переменными сообщение то же, только без untyped. Выхода два: печатать через запятую, как в день 01, или через Printf.


fmt.Printf: шаблон и значения

Ситуация. Нужна строка Склад Север: 12 шт. — двоеточие вплотную к имени, а Println поставит перед ним пробел. Printf печатает шаблон с местами для значений:

Код для чтения · разбираем, набирать не нужно
	name := "Север"
	qty := 12
	weight := 2.5
	fmt.Printf("Склад %s: %d шт.\n", name, qty)
	fmt.Printf("Вес: %.2f кг\n", weight)
	fmt.Printf("%v | %v | %v | %v\n", name, qty, weight, true)
	fmt.Printf("%T %T %T %T\n", name, qty, weight, true)
Вывод · это печатает программа, набирать не нужно
Склад Север: 12 шт.
Вес: 2.50 кг
Север | 12 | 2.5 | true
string int float64 bool

Как это читать. Первый аргумент — шаблон, а каждое % с буквой — глагол: место для следующего значения из списка, по порядку.

Глагол Что печатает Пример
%d целое число (decimal) 12
%s строку Север
%.2f дробное, ровно два знака после точки, с округлением 2.50
%02d целое, не меньше двух цифр, недостающие — нули 05
%v любое значение, как его напечатал бы Println 2.5, true
%T тип значения float64
%% сам знак % %

Вот и способ узнать тип, обещанный на первой странице: %T.

Printf ничего не добавляет сам — ни пробелов, ни перевода строки. Поэтому шаблон почти всегда кончается на \n: без него к выводу приклеится приглашение терминала:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
Итого: 5stagiaire@lab:~/gocourse/day03/scratch$

Копейки печатает %02d: ширина 2 — «не меньше двух символов», 0 перед ней — «дополнить нулями». Пятёрка станет 05, 45 останется 45, а %d напечатает 5. Округление у %.2f настоящее: 18.3366 даёт 18.34, 7.0 — 7.00.


Попробуйте сейчас: печать по шаблону.

Цель: вывод Printf совпал с показанным выше, и видно, что делают \n и %02d.

  1. Вставьте в тело main строки из примера со Склад Север и три строки про копейки:
✎ Наберите в файл scratch/main.go
	name := "Север"
	qty := 12
	weight := 2.5
	fmt.Printf("Склад %s: %d шт.\n", name, qty)
	fmt.Printf("Вес: %.2f кг\n", weight)
	fmt.Printf("%v | %v | %v | %v\n", name, qty, weight, true)
	fmt.Printf("%T %T %T %T\n", name, qty, weight, true)
	fmt.Printf("%d руб. %02d коп.\n", 123, 5)
	fmt.Printf("%d руб. %d коп.\n", 123, 5)
	fmt.Printf("Скидка: %d%%\n", 15)
  1. Запустите и сверьте первые четыре строки с выводом выше, последние три — между собой:
▶ Выполните
cd ~/gocourse/day03/scratch
go run .
Вывод · это печатает программа, набирать не нужно
123 руб. 05 коп.
123 руб. 5 коп.
Скидка: 15%
  1. Уберите \n в конце первого шаблона и запустите ещё раз: где теперь начинается строка Вес?

Готово, когда: вывод совпал с показанным и видно, где начинается строка Вес после второго запуска.


Ошибка в шаблоне: компилятор молчит

Шаблон — обычная строка, и компилятор в неё не заглядывает: программа с неправильным шаблоном собирается, запускается и печатает на месте ошибки странную вставку. В строке, которая печатается раз в месяц, такую ошибку никто не заметит, пока она не попадёт в чек покупателю.

Для этого есть go vet: он читает код, не запуская его, и ищет подозрительные места, которые компилятор пропускает; шаблоны Printf — главное из них.

Код для чтения · разбираем, набирать не нужно
	avg := 3.5
	sum := 10
	fmt.Printf("Средняя: %d\n", avg)
	fmt.Printf("Сумма: %.2f\n", sum)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
Средняя: %!d(float64=3.5)
Сумма: %!f(int=10)
stagiaire@lab:~/gocourse/day03/scratch$ go vet .
main.go:8:30: fmt.Printf format %d has arg avg of wrong type float64
main.go:9:26: fmt.Printf format %.2f has arg sum of wrong type int

Как это читать. Вставка в выводе: %! — «ошибка», дальше глагол, в скобках — что пришло вместо нужного. Сообщение vet — в знакомом формате: файл, строка, колонка, текст, только без строки # day03/scratch и без ./. Дословно: «формат %d получил аргумент avg неверного типа float64». Когда всё в порядке, go vet . молчит.

Ещё три находки vet — глагол без значения (%!d(MISSING)), лишнее значение (%!(EXTRA int=7)) и глагол в Println вместо Printf — с текстами сообщений разобраны в course extra 03.

Привычка на весь курс: перед сдачей — go vet ..


Что может пойти не так

Что видите Что это значит Что делать
invalid operation: "Итого: " + n (mismatched types untyped string and int) склеиваете строку с числом Println("Итого:", n) или Printf("Итого: %d\n", n)
приглашение приклеилось к выводу в шаблоне нет \n добавить \n
%!d(MISSING), %!d(string=…), %!(EXTRA …) шаблон и значения не сошлись go vet . покажет строку и причину
в выводе 75%!, на следующей строке (MISSING) одиночный % перед \n принят за начало глагола знак процента — %%
5 коп. вместо 05 коп. %d не дополняет нулями %02d

Попробуйте сейчас: go vet.

Цель: go vet . нашёл ошибку в шаблоне, а после правки промолчал.

  1. В scratch/main.go напишите:
✎ Наберите в файл scratch/main.go
package main

import "fmt"

func main() {
	total := 5
	fmt.Println("Итого: %d", total)
}
  1. Запустите программу и go vet:
▶ Выполните
cd ~/gocourse/day03/scratch
go run .
go vet .

go run . напечатает строку с %d внутри, go vet . назовёт причину.

  1. Замените Println на Printf, допишите \n в шаблон и запустите go vet . снова — он должен промолчать:
▶ Выполните
go vet .
  1. Сообщение vet — в словарик, через тире.

Готово, когда: второй go vet . ничего не напечатал; запуск засчитан в пункте проверки t_vet, в ~/errors.md новая строка с тире.


Попробуйте сейчас: задание avg.

Цель: avg/main.go печатает сумму и среднюю цену по TASK.txt, а go vet . молчит.

  1. Прочитайте условие — средняя цена трёх позиций:
▶ Выполните
cd ~/gocourse/day03/avg
cat TASK.txt
  1. Напишите программу в avg/main.go. Цены и сумма в копейках — целые. Средняя цена — величина не для учёта, а для показа: её считают в float64 до деления, и %.2f округляет её до копейки. Печать — Printf с %d и %.2f.
  2. Перед сдачей:
▶ Выполните
go run .
go vet .

Готово, когда: вывод совпал с тем, что требует TASK.txt, а go vet . ничего не напечатал — пункты проверки avg и vet.


Дальше: как считать деньги — course next