JavaRush /Курсы /Go SELF /Ввод данных в Go: fmt.Scan<...

Ввод данных в Go: fmt.Scan и fmt.Fscan

Go SELF
2 уровень , 3 лекция
Открыта

1. Введение

Когда вы пишете первые программы, они часто похожи на музыкальную шкатулку: открыл — играет одну и ту же мелодию. Это нормально на старте, но довольно быстро хочется, чтобы программа реагировала на пользователя или на входные данные из задачи: «введи два числа — получи сумму», «введи имя — получи приветствие».

В Go (и в большинстве учебных задач) основной путь — читать значения из стандартного ввода (stdin) и писать результат в стандартный вывод (stdout).

Главная цель этой лекции — сделать ввод предсказуемым. Чтобы вы точно понимали, сколько значений вы читаете, почему перед переменной ставится &, и что означают два возвращаемых результата у Scan/Fscan.

Как Scan видит ввод: пробелы и переводы строк — это разделители

Когда вы смотрите на входные данные глазами человека, вы видите «строчку», «абзац», «переносы». Когда на них смотрит fmt.Scan, он видит не «строчки», а последовательность значений, разделённых пробелами и переводами строки.

То есть для Scan обычно нет большой разницы, было ли 10 20 в одной строке или 10 на одной строке и 20 на следующей — это всё равно два значения.

flowchart LR
    A[stdin: '10  20 Bob'] --> B[fmt.Scan / fmt.Fscan]
    B --> C[токен 1: 10]
    B --> D[токен 2: 20]
    B --> E[токен 3: Bob]

Для новичка это очень удобная модель: вы заранее знаете «мне нужно 3 значения» — значит, вы зовёте Scan так, чтобы он попытался прочитать ровно 3 значения (или делаете несколько вызовов).

fmt.Scan: читаем одно значение

Давайте начнём с самого спокойного варианта: вводим одно целое число и печатаем его обратно. Это упражнение полезно не потому, что кому-то нужен «эхо‑бот», а потому что оно фиксирует механику: переменная должна существовать заранее, и Scan должен получить “куда записать”.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)
	fmt.Println(n) // если ввели 7, то выведет: 7
}

Здесь важно две вещи.

Первая: мы объявили n заранее через var n int. Ввод не создаёт переменные, он заполняет уже существующие.

Вторая: мы написали fmt.Scan(&n), а не fmt.Scan(n). И вот это & сейчас самый частый вопрос на всём старте Go.

Важные нюансы про &x

Если объяснять совсем без теории указателей (и мы так и сделаем), то &n можно читать так: «передаю функции место, куда она должна записать ответ».

Представьте, что переменная — это коробка с наклейкой n. Если вы вызываете fmt.Scan(n), вы как будто говорите: «Вот что лежит в коробке сейчас». Но Scan не нужен текущий предмет из коробки, ему нужна возможность положить новый предмет внутрь. Поэтому вы передаёте не содержимое коробки, а её “адрес”, то есть саму коробку: &n.

Немного более технически (но всё ещё “по‑бытовому”): &n — это адрес переменной n в памяти. Go не позволяет Scan возвращать значение напрямую через параметры, поэтому он пишет в переменную через адрес.

Если сделать ошибку и забыть &, компилятор обычно скажет что-то в духе: «нельзя использовать n (переменная типа int) как аргумент… ожидается указатель». Это тот случай, когда компилятор ворчит по делу: он буквально говорит «ты не дал мне коробку, ты дал мне число».

Читаем несколько значений за один раз

Очень типичный формат задач: «даны два целых числа a и b». Тогда ввод выглядит как a b (или на разных строках), и нам удобно считать сразу оба:

package main

import "fmt"

func main() {
	var a, b int
	fmt.Scan(&a, &b)

	sum := a + b
	fmt.Println(sum) // если ввели 10 3, то выведет: 13
}

Здесь Scan попытается прочитать ровно два значения: сначала в a, потом в b. Поэтому порядок переменных должен совпадать с порядком значений во входе.

Если вам дали вход 3 10, а вы сделали fmt.Scan(&b, &a), программа не “сломается” — она честно прочитает 3 в b, 10 в a. Сломается только ваше ожидание результата (а это иногда больнее).

2. Что возвращает Scan: (count, err)

Два результата: сколько считали и была ли ошибка

Многие функции Go возвращают не одно значение, а два (или больше). У fmt.Scan ровно такой случай: он возвращает количество успешно считанных значений и ошибку, если что-то пошло не так.

Почему так сделали в Go? Потому что в Go ошибки — это значения, с которыми работают явно: их можно вернуть, сохранить, напечатать, сравнить с nil. Этот подход известен как “errors are values”.

Посмотрим на это «в лоб», без умных обработок: просто считаем число и печатаем, что вернул Scan.

package main

import "fmt"

func main() {
	var x int
	count, err := fmt.Scan(&x)

	fmt.Println("count:", count) // например: count: 1
	fmt.Println("err:", err)     // при успехе: err: <nil>
	fmt.Println("x:", x)         // если ввели 42, то: x: 42
}

Как это читать.

count — это сколько значений реально удалось распознать и записать в переменные. Если вы просили одно значение, то при нормальном вводе count будет 1.

err — это ошибка. Если ошибки нет, err равен nil. Это важная деталь: nil означает “ничего нет”, в данном случае — “ошибки нет”.

Здесь мы не строим сложную логику “что делать при ошибке” (это будет позже, когда вы познакомитесь с условными операторами), но уже сейчас полезно научиться хотя бы понимать диагностику и не пугаться <nil>: <nil> — это хорошая новость.

Почему count иногда меньше, чем вы ожидали

Бывает ситуация: вы написали fmt.Scan(&a, &b, &c), ожидая три значения, но во входе дали только два. Или вместо числа пришло слово. Тогда Scan не может корректно заполнить все переменные.

В такой момент (обычно) происходит следующее: count будет показывать, сколько значений успели считать, а err станет не nil (то есть будет какая-то ошибка).

Давайте смоделируем чтение двух чисел, но представим, что во входе внезапно оказалось “10 котик”.

package main

import "fmt"

func main() {
	var a, b int
	count, err := fmt.Scan(&a, &b)

	fmt.Println("count:", count) // вероятно: count: 1
	fmt.Println("err:", err)     // будет не <nil>, там описание ошибки
	fmt.Println("a:", a)         // a: 10
	fmt.Println("b:", b)         // b: 0 (останется нулевым значением)
}

Почему b стал 0? Потому что b успели объявить (var b int), а int по умолчанию равен нулю, и если ввод не смог записать новое значение — остаётся “как было”.

И ещё раз подчеркну: вы сейчас не обязаны строить обработку ошибок “правильно”. Но вы уже можете понимать поведение: “часть ввода считалась, потом случилась ошибка”.

Blank identifier _: когда вы не хотите хранить один из результатов

Иногда вам не нужен count. В большинстве задач формата “ввод гарантирован корректный” вы действительно не используете count, потому что ожидаете, что всё прочитается нормально.

В Go принято явно показывать: «я осознанно игнорирую этот результат». Для этого есть специальное имя _ (подчёркивание), его называют blank identifier.

package main

import "fmt"

func main() {
	var n int
	_, err := fmt.Scan(&n)

	fmt.Println("err:", err) // при успехе: err: <nil>
	fmt.Println(n)           // например: 5
}

Почему это полезно, даже если вы пока не обрабатываете ошибки? Потому что код становится честнее: вы не делаете вид, что count важен, и не плодите переменные “на всякий случай”.

3. Паника в Go

Иногда программа в Go не “возвращает ошибку”, а аварийно прекращает работу. Это называется panic.

Паника — это сигнал: “произошло что-то настолько неправильное, что продолжать выполнение опасно или бессмысленно”. Чаще всего panic возникает из-за багов в коде или серьезных сбоев в данных.

Важно не путать:

  • err — это обычная ошибка, с ней можно работать: проверить, вывести сообщение, вернуть наверх.
  • panic — это падение программы: Go печатает сообщение об ошибке, место где она произошла, и выполнение останавливается.

Пример типичной паники, которую новички встретят довольно рано:

package main
import "fmt"

func main() {
	x := 10
	y := 0
	fmt.Println(x / y) // panic: runtime error: integer divide by zero
}

Если вы знакомы с концепцией исключений, то panic похож на exception, только используется при сильных сбоях.

Поэтому если программа внезапно упала и вы видите слово panic, это почти всегда означает ошибку логики (или неверные предположения о данных). А вот fmt.Scan обычно не паникует — он возвращает err, чтобы вы могли обработать ситуацию спокойно.

4. fmt.Fscan: ввод с явным источником os.Stdin

fmt.Scan удобен тем, что он читает из стандартного ввода “сам”. Но иногда вам нужно чуть больше контроля: например, вы хотите читать не из stdin, а из другого источника (файла, строки, сети). Тогда используется fmt.Fscan, где первая вещь — это откуда читать.

На нашем текущем уровне нам достаточно познакомиться с самым простым вариантом: os.Stdin — это тот же стандартный ввод, просто переданный явно.

package main

import (
	"fmt"
	"os"
)

func main() {
	var a, b int
	fmt.Fscan(os.Stdin, &a, &b)

	fmt.Println(a * b) // если ввели 4 5, то выведет: 20
}

С практической точки зрения в учебных задачах Scan и Fscan(os.Stdin, ...) часто взаимозаменяемы. Но “идея” Fscan важна: функция ввода может читать не только “из консоли”, и в Go это делается через явную передачу источника.

Если вы видите в чужом решении fmt.Fscan(...), не пугайтесь: это всё тот же Scan, только с уточнением “читать вот отсюда”.

5. Мини‑приложение: «Калькулятор счёта»

Чтобы закрепить ввод в контексте нормальной программы, продолжим наш маленький учебный проект (пусть он будет называться calcbox). До этого он умел только печатать и считать на заранее заданных значениях. Теперь он будет читать числа из ввода и делать вычисление.

Сценарий простой: вводим цену и количество, печатаем итоговую стоимость. Никаких процентов и строк‑чисел — всё целочисленно, чтобы не смешивать темы.

package main

import "fmt"

func main() {
	// read
	var price int
	var qty int
	fmt.Scan(&price, &qty)

	// compute
	total := price * qty

	// print
	fmt.Println(total) // если ввели 120 3, то выведет: 360
}

Обратите внимание, как органично сюда легли правила из прошлых лекций. Переменные объявлены заранее, ввод идёт через &, вычисление — отдельной строкой, вывод — в конце. Даже если программа маленькая, такой порядок спасает мозг от перегрева.

Если захотите сделать отладочный режим “посмотреть, что прочиталось”, вы уже умеете временно добавить печать count и err, а потом убрать.

6. Типичные ошибки при вводе через fmt.Scan и fmt.Fscan

Ошибка №1: забыли & перед переменной.
Это самая частая история. Вы пишете fmt.Scan(n) и ожидаете, что n заполнится, но компилятор ругается. Причина простая: Scan должен куда-то записать значение, а без & вы передали не “место”, а текущее содержимое. Думайте “передаю коробку, а не предмет в коробке”.

Ошибка №2: переменная не объявлена до ввода.
Иногда хочется написать что-то вроде fmt.Scan(&x) без предварительного var x int. Но функция не может записать в “переменную, которой нет”. Сначала объявляем, потом сканируем. В начале лучше писать чуть более многословно, зато без магии.

Ошибка №3: перепутан порядок чтения.
fmt.Scan(&a, &b) читает сначала a, потом b. Если во входе порядок другой, результат будет “не тот”, хотя программа формально работает. Это неприятный класс ошибок: код компилируется, запускается, но ответ неверный. Лечится вниманием к формату входных данных и говорящими именами переменных.

Ошибка №4: ожидали, что Scan прочитает строку целиком вместе с пробелами.
Scan читает токены, разделённые пробелами. Если вы вводите "John Smith", то в string через Scan попадёт только "John". Это не баг, это правило. Просто сейчас мы работаем с “словами” и “числами”, а не с “целыми строками текста”.

Ошибка №5: игнорируют (count, err) и потом долго не понимают, почему переменная осталась нулевой.
Да, по условиям многих задач ввод гарантирован корректный. Но когда вы отлаживаете свой код или случайно вводите не то, err — единственная подсказка, что произошло. Подход “ошибки — это значения” как раз и существует, чтобы их можно было увидеть и понять.

1
Задача
Go SELF, 2 уровень, 3 лекция
Недоступна
Эхо терминала
Эхо терминала
1
Задача
Go SELF, 2 уровень, 3 лекция
Недоступна
Очки команды
Очки команды
1
Задача
Go SELF, 2 уровень, 3 лекция
Недоступна
Диагностика ввода
Диагностика ввода
1
Задача
Go SELF, 2 уровень, 3 лекция
Недоступна
Чек умножения
Чек умножения
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ruslan Уровень 4
3 апреля 2026
Для решения последнего задания нужно использовать форматированный вывод со спецификаторами. Printf : https://pkg.go.dev/fmt#Printf Пример из доки, где %s и %d спецификаторы для типа значения, которое (значение) будет подставлено в строку переданными аргументами name и age :

package main

import (
	"fmt"
)

func main() {
	const name, age = "Kim", 22
	fmt.Printf("%s is %d years old.\n", name, age)

	// It is conventional not to worry about any
	// error returned by Printf.

}
Victor Gasparyan Уровень 38
16 апреля 2026
Можно решить, используя fmt.Print() Важно не забыть в конце передать последним аргументом перевод строки ("\n")