Множественный ввод: 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

Полезные ссылки