Интерфейс fmt.ScanState
Интерфейс fmt.ScanState представляет собой состояние сканера,
которое передается в пользовательские методы сканирования.
Он позволяет получить доступ к токенам, прочитанным из входного потока,
и управлять процессом сканирования.
Данный интерфейс реализуется внутренними структурами пакета fmt
и используется в функциях семейства Scan и Fscan.
Основное назначение интерфейса - предоставить разработчикам возможность
создавать собственные типы, которые могут быть считаны из входного потока.
Методы интерфейса
Интерфейс fmt.ScanState содержит следующие методы:
type ScanState interface {
ReadRune() (r rune, size int, err error)
UnreadRune() error
SkipSpace() error
Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
Width() (wid int, ok bool)
Read(buf []byte) (n int, err error)
}
Метод ReadRune читает один рунический символ из потока.
Метод UnreadRune отменяет чтение последнего рунического символа.
Метод SkipSpace пропускает все пробельные символы в потоке.
Метод Token читает токен из потока, используя функцию для определения границ токена.
Метод Width возвращает ширину, указанную в формате сканирования.
Метод Read читает байты в переданный срез.
Пример создания пользовательского типа для сканирования
Создадим пользовательский тип, который реализует интерфейс Scanner
и использует fmt.ScanState для чтения данных:
package main
import (
"fmt"
"strings"
)
type Point struct {
X, Y int
}
func (p *Point) Scan(state fmt.ScanState, verb rune) error {
state.SkipSpace()
state.ReadRune() // skip '('
fmt.Fscanf(state, "%d", &p.X)
state.ReadRune() // skip ','
fmt.Fscanf(state, "%d", &p.Y)
state.ReadRune() // skip ')'
return nil
}
func main() {
var p Point
input := "(10,20)"
fmt.Sscan(input, &p)
fmt.Printf("%+v", p)
}
Результат выполнения кода:
{X:10 Y:20}
В данном примере мы создали структуру Point, которая реализует
метод Scan. Внутри метода мы используем state.SkipSpace
для пропуска пробелов и state.ReadRune для чтения символов.
Пример использования метода Token
Метод Token позволяет читать токены, определяя их границы
с помощью пользовательской функции:
package main
import (
"fmt"
"strings"
)
type Word struct {
Value string
}
func (w *Word) Scan(state fmt.ScanState, verb rune) error {
token, err := state.Token(false, func(r rune) bool {
return r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z'
})
if err != nil {
return err
}
w.Value = string(token)
return nil
}
func main() {
var w Word
input := "Hello123World"
fmt.Sscan(input, &w)
fmt.Printf("%+v", w)
}
Результат выполнения кода:
{Value:Hello}
В этом примере метод Token читает только буквенные символы,
останавливаясь на цифрах. В результате мы получаем первое слово "Hello".
Пример использования ширины
Метод Width позволяет узнать, задана ли ширина в формате
и использовать ее для ограничения чтения:
package main
import (
"fmt"
"strings"
)
type FixedString struct {
Value string
}
func (fs *FixedString) Scan(state fmt.ScanState, verb rune) error {
width, ok := state.Width()
if !ok {
return fmt.Errorf("width not specified")
}
buf := make([]byte, width)
n, err := state.Read(buf)
if err != nil {
return err
}
fs.Value = string(buf[:n])
return nil
}
func main() {
var fs FixedString
input := "abcdefghij"
fmt.Sscanf(input, "%5s", &fs)
fmt.Printf("%+v", fs)
}
Результат выполнения кода:
{Value:abcde}
В этом примере мы используем ширину для чтения ровно 5 символов.
Формат %5s указывает, что нужно прочитать 5 байт.
Пример обработки ошибок
Рассмотрим пример с обработкой ошибок при сканировании:
package main
import (
"fmt"
"strings"
)
type IntPair struct {
First, Second int
}
func (ip *IntPair) Scan(state fmt.ScanState, verb rune) error {
state.SkipSpace()
_, err := state.ReadRune()
if err != nil {
return fmt.Errorf("expected '(' but got EOF")
}
_, err = fmt.Fscan(state, &ip.First)
if err != nil {
return fmt.Errorf("invalid first number: %v", err)
}
_, err = state.ReadRune()
if err != nil {
return fmt.Errorf("expected ',' but got EOF")
}
_, err = fmt.Fscan(state, &ip.Second)
if err != nil {
return fmt.Errorf("invalid second number: %v", err)
}
_, err = state.ReadRune()
if err != nil {
return fmt.Errorf("expected ')' but got EOF")
}
return nil
}
func main() {
var ip IntPair
input := "(5,10)"
_, err := fmt.Sscan(input, &ip)
if err != nil {
fmt.Printf("Error: %v", err)
return
}
fmt.Printf("%+v", ip)
}
Результат выполнения кода:
{First:5 Second:10}
Данный пример демонстрирует правильную обработку ошибок при чтении различных частей входных данных.
Смотрите также
-
интерфейс
fmt.Scanner,
который реализуется для создания пользовательских типов -
интерфейс
fmt.State,
который используется при форматировании -
функцию
fmt.Sscan,
которая сканирует строку -
функцию
fmt.Sscanf,
которая сканирует строку с форматом