Урок 3. Ввод и вывод — fmt.Scan, bufio и Printf
Программа становится полезной, когда работает с данными пользователя. В этом уроке — как прочитать числа и строки с клавиатуры и как вывести результат в нужном виде.
Чтение чисел: fmt.Scan
package main
import "fmt"
func main() {
var a, b int
fmt.Scan(&a, &b)
fmt.Println(a + b)
fmt.Println(a * b)
}3 47
12fmt.Scan читает значения, разделённые пробелами или переводами строк, и кладёт их в переменные по порядку. Числа можно ввести через пробел или каждое на своей строке — результат один.
Знак & перед именем значит «адрес переменной». Функции нужно не значение a, а место, куда записать прочитанное. Без & программа соберётся, но Scan вернёт ошибку и переменная останется нулём. Об адресах (указателях) — в уроке Структуры, методы и интерфейсы.
Тип переменной подсказывает Scan, что читать: в int — целое, в float64 — дробное, в string — одно слово.
package main
import "fmt"
func main() {
var name string
var price float64
var count int
fmt.Scan(&name, &price, &count)
fmt.Println(name, price*float64(count))
}хлеб 1.5 4хлеб 6Дробное число вводится через точку: 1.5. Запятую Scan не поймёт.
Если ввели не то
fmt.Scan возвращает два значения: сколько переменных удалось заполнить и ошибку. Обычно их не проверяют в учебных задачах, но в настоящей программе стоит:
package main
import "fmt"
func main() {
var n int
if _, err := fmt.Scan(&n); err != nil {
fmt.Println("нужно целое число")
return
}
fmt.Println("квадрат:", n*n)
}abcнужно целое число_ — пустое имя: значение, которое не нужно, отбрасывается. Конструкцию if … ; err != nil разбирают в уроках Условия, switch и цикл for и Функции и ошибки.
Чтение строки целиком: bufio.Scanner
fmt.Scan в строку читает только одно слово — до пробела. Чтобы прочитать строку с пробелами, нужен bufio.Scanner:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
name := scanner.Text()
fmt.Println("Привет,", name+"!")
}Анна ИвановаПривет, Анна Иванова!os.Stdin — стандартный ввод (клавиатура), scanner.Scan() читает очередную строку, scanner.Text() возвращает её без перевода строки. Как читать строки в цикле до конца ввода и что делать с очень длинными строками — в справке Как прочитать строку с пробелами.
Не смешивайте в одной программе fmt.Scan и bufio.Scanner: Scanner читает ввод вперёд большими кусками, и fmt.Scan после него может не увидеть часть данных. Выберите один способ — например, читайте всё через Scanner, а числа получайте из строк функцией strconv.Atoi.
Вывод по формату: fmt.Printf
Println выводит значения как есть. Когда нужен определённый вид — два знака после точки, выравнивание в колонку, — используют Printf. Первый аргумент — строка формата, в ней на месте значений стоят глаголы: %d, %s и другие.
package main
import "fmt"
func main() {
name := "Анна"
age := 20
height := 1.68
fmt.Printf("%s, %d лет, рост %.2f м\n", name, age, height)
fmt.Printf("%v %v %v\n", name, age, height)
fmt.Printf("%q %T %T\n", name, age, height)
fmt.Printf("[%5d] [%-5d] [%05d]\n", age, age, age)
fmt.Printf("%.1f%%\n", 12.345)
}Анна, 20 лет, рост 1.68 м
Анна 20 1.68
"Анна" int float64
[ 20] [20 ] [00020]
12.3%| Глагол | Что выводит |
|---|---|
%d | целое число |
%f, %.2f | дробное; .2 — два знака после точки |
%s | строку |
%q | строку в кавычках — видно пробелы и пустую строку |
%v | любое значение в обычном виде |
%T | тип значения |
%t | true или false |
%c | символ по его коду (rune) |
%x | число в шестнадцатеричном виде |
%% | сам знак процента |
Число между % и буквой задаёт ширину поля: %5d — пять знаков, выравнивание вправо, %-5d — влево, %05d — с нулями впереди. Printf не добавляет перевод строки — в конце формата пишут \n.
Если глагол не подходит к значению, Go не падает, а печатает подсказку прямо в выводе: %!d(string=Анна). Увидели такое — проверьте формат.
Строка по формату: fmt.Sprintf
Sprintf устроен так же, как Printf, но ничего не печатает, а возвращает готовую строку:
package main
import "fmt"
func main() {
code := fmt.Sprintf("BY-%04d", 42)
fmt.Println(code)
fmt.Println(len(code))
}BY-0042
7Быстрый ввод и вывод
fmt.Scan и fmt.Println работают напрямую с системой, без буфера — промежуточной памяти, где данные копятся и передаются большими порциями. На десятках чисел это незаметно, а на сотнях тысяч — например, в олимпиадных задачах — программа может не уложиться во время. Тогда ввод и вывод оборачивают в буферы из пакета bufio:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var a, b int
fmt.Fscan(in, &a, &b)
fmt.Fprintln(out, a*b)
}6 742fmt.Fscan и fmt.Fprintln — те же Scan и Println, только первым аргументом указывают, откуда читать и куда писать. Вывод копится в буфере и уходит на экран при вызове out.Flush(). Строка defer out.Flush() откладывает этот вызов до выхода из main — подробно в справке defer. Без Flush часть вывода, а то и весь, пропадёт.
Частые ошибки
- Забыт
&:fmt.Scan(a)— переменная не меняется. - Строка с пробелами через
fmt.Scan— прочитается только первое слово. Printfбез\n— следующий вывод приклеится к этой строке.- Буферизованный вывод без
Flush— вывод пропадает. - Запятая в дробном числе при вводе:
1,5вместо1.5.
Коротко
fmt.Scan(&a, &b)читает значения через пробелы и переводы строк;&обязателен.- Строку с пробелами читает
bufio.NewScanner(os.Stdin):Scan(), затемText(). fmt.Printfвыводит по формату:%d,%s,%.2f,%v,%T; перевод строки —\n.fmt.Sprintfвозвращает строку вместо вывода.- Для больших данных —
bufio.NewReader,bufio.NewWriterиdefer out.Flush().