Глиф Учебники

Урок 3. Ввод и вывод — fmt.Scan, bufio и Printf

Курс «Go» · урок 3 из 10 · 4 минуты чтения

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

Чтение чисел: fmt.Scan

Go
package main

import "fmt"

func main() {
    var a, b int
    fmt.Scan(&a, &b)
    fmt.Println(a + b)
    fmt.Println(a * b)
}
Ввод
3 4
Вывод
7
12

fmt.Scan читает значения, разделённые пробелами или переводами строк, и кладёт их в переменные по порядку. Числа можно ввести через пробел или каждое на своей строке — результат один.

Знак & перед именем значит «адрес переменной». Функции нужно не значение a, а место, куда записать прочитанное. Без & программа соберётся, но Scan вернёт ошибку и переменная останется нулём. Об адресах (указателях) — в уроке Структуры, методы и интерфейсы.

ввод 3 4 fmt.Scan(&a, &b) берёт значения по порядку &a &b a 3 b 4 записано по адресу без & fmt.Scan(a) 0 в функцию попала копия значения: a остаётся 0, Scan возвращает ошибку
fmt.Scan(&a, &b) получает адреса переменных и записывает в a и b числа 3 и 4 из ввода, а без & в функцию попадает копия значения 0: переменная не меняется, и Scan возвращает ошибку.

Тип переменной подсказывает Scan, что читать: в int — целое, в float64 — дробное, в string — одно слово.

Go
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 возвращает два значения: сколько переменных удалось заполнить и ошибку. Обычно их не проверяют в учебных задачах, но в настоящей программе стоит:

Go
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:

Go
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() возвращает её без перевода строки. Как читать строки в цикле до конца ввода и что делать с очень длинными строками — в справке Как прочитать строку с пробелами.

ввод с клавиатуры Анна Иванова \n fmt.Scan Анна одно слово — до пробела scanner.Text() Анна Иванова вся строка, без \n
При вводе «Анна Иванова» fmt.Scan читает в строку одно слово — до пробела, а scanner.Text() возвращает всю строку без перевода строки.

Не смешивайте в одной программе fmt.Scan и bufio.Scanner: Scanner читает ввод вперёд большими кусками, и fmt.Scan после него может не увидеть часть данных. Выберите один способ — например, читайте всё через Scanner, а числа получайте из строк функцией strconv.Atoi.

Вывод по формату: fmt.Printf

Println выводит значения как есть. Когда нужен определённый вид — два знака после точки, выравнивание в колонку, — используют Printf. Первый аргумент — строка формата, в ней на месте значений стоят глаголы: %d, %s и другие.

Go
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тип значения
%ttrue или false
%cсимвол по его коду (rune)
%xчисло в шестнадцатеричном виде
%%сам знак процента

Число между % и буквой задаёт ширину поля: %5d — пять знаков, выравнивание вправо, %-5d — влево, %05d — с нулями впереди. Printf не добавляет перевод строки — в конце формата пишут \n.

строка формата "%s, %d лет, рост %.2f м\n" name "Анна" age 20 height 1.68 вывод Анна, 20 лет, рост 1.68 м ширина поля 5 %5d вправо 2 0 %-5d влево 2 0 %05d нули впереди 0 0 0 2 0 — пробел
Глаголы строки формата по порядку заменяются аргументами: %s — значением name, %d — age, %.2f — height с двумя знаками после точки; число 5 в %5d, %-5d и %05d задаёт ширину поля.

Если глагол не подходит к значению, Go не падает, а печатает подсказку прямо в выводе: %!d(string=Анна). Увидели такое — проверьте формат.

Строка по формату: fmt.Sprintf

Sprintf устроен так же, как Printf, но ничего не печатает, а возвращает готовую строку:

Go
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:

Go
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 7
Вывод
42

fmt.Fscan и fmt.Fprintln — те же Scan и Println, только первым аргументом указывают, откуда читать и куда писать. Вывод копится в буфере и уходит на экран при вызове out.Flush(). Строка defer out.Flush() откладывает этот вызов до выхода из main — подробно в справке defer. Без Flush часть вывода, а то и весь, пропадёт.

Частые ошибки

Коротко

Проверьте себя

1. Что прочитает fmt.Scan(&s) в переменную s string, если ввести Новый год?
2. Что выведет fmt.Printf("%.2f\n", 3.14159)?
3. Зачем перед переменной в fmt.Scan(&n) ставят &?
4. Что случится, если писать через bufio.NewWriter(os.Stdout) и не вызвать Flush?