Множественный ввод: Scanln, Scan и Scanf
Краткое описание
Изучаем три способа ввода данных: fmt.Scanln() для множественного ввода через пробел, fmt.Scan() для ввода с переносами строк и fmt.Scanf() для ввода по шаблону. Разбираем особенности каждого метода, проблемы с многословными строками и нюансы работы с буфером.
Три метода ввода
1. fmt.Scanln() — множественный ввод через пробел
Синтаксис: можно считывать несколько переменных одной строкой.
var x, y int
fmt.Print("Введите x и y через пробел: ")
fmt.Scanln(&x, &y)
fmt.Printf("x = %d, y = %d\n", x, y)
// Ввод: 123 456
// Вывод: x = 123, y = 456
Можно смешивать типы:
var x, y int
var name string
fmt.Print("Введите x, y и name через пробел: ")
fmt.Scanln(&x, &y, &name)
fmt.Printf("x = %d, y = %d, name = %s\n", x, y, name)
// Ввод: 123 456 John
// Вывод: x = 123, y = 456, name = John
⚠️ Проблема с многословными строками:
var name string
fmt.Print("Введите имя: ")
fmt.Scanln(&name)
fmt.Printf("name = %s\n", name)
// Ввод: John Doe
// Вывод: name = John (только первое слово!)
// "Doe" остаётся в буфере и вызывает проблемы
Почему: Scanln() считает пробел разделителем между переменными. Для одной переменной ввод останавливается на первом пробеле.
Вывод: Scanln() подходит только для однословных строк.
2. fmt.Scan() — ввод с переносами строк
Отличие от Scanln: не останавливается на символе новой строки (\n), продолжает ждать данные.
var x, y int
var name string
fmt.Println("Введите x, y и name (каждое с новой строки):")
fmt.Scan(&x, &y, &name)
fmt.Printf("x = %d, y = %d, name = %s\n", x, y, name)
// Ввод (по строкам):
// 123
// 456
// John
// [Enter для завершения]
// Вывод: x = 123, y = 456, name = John
⚠️ Проблема с буфером:
После Scan() может остаться символ \n в буфере, что мешает следующему вводу.
Решение — очистка буфера:
var x, y int
fmt.Scan(&x, &y)
// Очистка буфера
var dummy string
fmt.Scanln(&dummy) // Считывает оставшийся '\n'
// Теперь следующий ввод работает корректно
var name string
fmt.Scanln(&name)
Практический совет: Scan() редко используется в простых программах из-за сложностей с буфером.
3. fmt.Scanf() — ввод по шаблону
Применение: когда нужно считать данные в строго определённом формате.
var day, month, year int
fmt.Println("Введите дату в формате ДД-ММ-ГГГГ:")
fmt.Scanf("%d-%d-%d", &day, &month, &year)
fmt.Printf("Дата: %d.%d.%d г.\n", day, month, year)
// Ввод: 12-10-2024
// Вывод: Дата: 12.10.2024 г.
Шаблон указывает разделители:
// Дата с точками
fmt.Scanf("%d.%d.%d", &day, &month, &year)
// Ввод: 12.10.2024
// Время
var hour, minute, second int
fmt.Scanf("%d:%d:%d", &hour, &minute, &second)
// Ввод: 14:30:45
// Координаты
var x, y float64
fmt.Scanf("%f,%f", &x, &y)
// Ввод: 3.14,2.71
Преимущество: строгий формат + автоматический парсинг.
Полный пример всех трёх методов
package main
import "fmt"
func main() {
// 1. Scanln — множественный ввод через пробел
var x, y int
fmt.Print("Введите x и y через пробел: ")
fmt.Scanln(&x, &y)
fmt.Printf("x = %d, y = %d\n\n", x, y)
// 2. Scan — ввод с переносами (с очисткой буфера)
var a, b int
fmt.Println("Введите a и b (каждое с новой строки):")
fmt.Scan(&a, &b)
// Очистка буфера
var dummy string
fmt.Scanln(&dummy)
fmt.Printf("a = %d, b = %d\n\n", a, b)
// 3. Scanf — ввод по шаблону
var day, month, year int
fmt.Println("Введите дату в формате ДД-ММ-ГГГГ:")
fmt.Scanf("%d-%d-%d", &day, &month, &year)
fmt.Printf("Дата: %d.%d.%d г.\n", day, month, year)
}
Сравнительная таблица
| Метод | Разделитель | Перенос строк | Шаблон | Сложность |
|---|---|---|---|---|
Scanln |
Пробел | Останавливается | Нет | Простая |
Scan |
Пробел/перенос | Игнорирует | Нет | Средняя |
Scanf |
Заданный | По шаблону | Да | Средняя |
Важные моменты
1. Scanln останавливается на новой строке
// Если ввести данные с переносами, считается только первая строка
fmt.Scanln(&x, &y)
// Ввод:
// 123
// 456
// Результат: x = 123, y = 0 (вторая строка не считана)
2. Пробел = разделитель в Scanln
var name string
fmt.Scanln(&name)
// Ввод: "John Doe"
// Результат: name = "John" (только первое слово)
3. Scan требует очистки буфера
fmt.Scan(&value)
var dummy string
fmt.Scanln(&dummy) // Очистка '\n'
4. Scanf строго проверяет формат
fmt.Scanf("%d-%d-%d", &d, &m, &y)
// Ввод: 12-10-2024 ✅
// Ввод: 12.10.2024 ❌ (не соответствует шаблону)
5. Рекомендация автора
Для простых примеров достаточно fmt.Scanln(). Для сложных сценариев — специализированные библиотеки.
Типичные ошибки
Ошибка #1: Многословная строка в Scanln
var fullName string
fmt.Scanln(&fullName)
// Ввод: "John Doe"
// Проблема: считается только "John"
Ошибка #2: Забыли очистить буфер после Scan
fmt.Scan(&x)
fmt.Scanln(&name) // Может считать остаток '\n' вместо реального ввода
Ошибка #3: Неправильный шаблон в Scanf
fmt.Scanf("%d.%d.%d", &d, &m, &y)
// Ввод: 12-10-2024
// Проблема: ожидается точка, введён дефис
Best Practices
1. Для простого ввода — Scanln
var age int
fmt.Print("Введите возраст: ")
fmt.Scanln(&age)
2. Для структурированных данных — Scanf
fmt.Scanf("%d-%d-%d", &day, &month, &year)
3. Показывайте формат ввода
fmt.Println("Введите дату в формате ДД-ММ-ГГГГ:")
fmt.Scanf("%d-%d-%d", &d, &m, &y)
4. Избегайте Scan без необходимости
Из-за сложностей с буфером лучше использовать Scanln или Scanf.
5. Однословные строки — Scanln
var username string
fmt.Scanln(&username) // ✅ Для логина
6. Многословные строки — используйте bufio (следующий урок)
// Для "John Doe" нужен другой подход
Что запомнить
Scanln(&a, &b)— множественный ввод через пробелScan(&a, &b)— ввод с переносами строк, нужна очистка буфераScanf("шаблон", &a, &b)— ввод по строгому форматуScanlnостанавливается на\n(Enter)Scanигнорирует\n, продолжает ждать- Пробел в
Scanln= разделитель переменных - Многословные строки не работают с
Scanln - Шаблон в
Scanfзадаёт разделители:"%d-%d-%d","%d:%d:%d" - Очистка буфера:
var dummy string; fmt.Scanln(&dummy) - Рекомендация: для простых задач —
Scanln, для формата —Scanf