cpp

Ввод данных

Выводить данные на консоль мы научились, теперь рассмотрим функции, предназначенные для получения данных от пользователя. Сначала познакомимся с функцией Scan() из пакета fmt. Формат функции:

fmt.Scan(a ...interface{}) (n int, err error)

Функция принимает один или несколько параметров через запятую. В качестве значения нужно передать адрес переменной, в которую будет записано прочитанное значение. Чтобы передать адрес следует перед названием переменной добавить символ &. Функция Scan() возвращает два значения. Первое значение содержит количество успешных записей. Через второе значение можно получить сообщение об ошибке. Если ошибка не возникла, то значением будет nil.

Пример получения целочисленного значения:

x := 0                  // Объявление переменной
fmt.Print("x = ")       // Вывод подсказки
n, err := fmt.Scan(&x)  // Ввод числа
fmt.Println(n, err)
fmt.Println("Значение x =", x)

Запустим программу на выполнение и введем значение 20:

C:\book\test>go run main.go
x = 20
1 <nil>
Значение x = 20

Итак, в переменную x было записано значение 20. Переменная n получила значение 1 (одна успешная запись), а переменная err — значение nil, которое означает, что ошибки не возникло.

Следует учитывать, что пользователь может ввести все что угодно, например, строку:

C:\book\test>go run main.go
x = строка
0 expected integer
Значение x = 0

Произвольную строку нельзя преобразовать в число, поэтому переменная n получила значение 0 (нет успешных записей), а переменная err — описание ошибки.

Пользователь может ввести несколько чисел через пробел, в этом случае будет записано первое значение, а оставшаяся часть ввода останется в потоке и будет доступна для следующей операции чтения:

C:\book\test>go run main.go
x = 10 20 30
1 <nil>
Значение x = 10

В качестве примера произведем суммирование двух целых чисел, введенных пользователем (листинг 1.3).

Листинг 1.3. Суммирование двух введенных чисел

package main

import (
   "fmt"
   "os"
)

func main() {
   x, y := 0, 0
   fmt.Print("x = ")         // Вывод подсказки
   _, err := fmt.Scan(&x)    // Ввод числа
   if err != nil {           // Проверяем успешность ввода
      fmt.Fprintln(os.Stderr, "Вы ввели не число")
      return                 // Завершаем программу
   }
   fmt.Print("y = ")         // Вывод подсказки
   _, err = fmt.Scan(&y)     // Ввод числа
   if err != nil {           // Проверяем успешность ввода
      fmt.Fprintln(os.Stderr, "Вы ввели не число")
      return                 // Завершаем программу
   }
   fmt.Println("Сумма равна:", x + y)
}

В первой строке объявляется пакет с названием main. Далее с помощью инструкции import подключаются пакеты fmt и os. Пакет fmt нужен для использования функций Print(), Println(), Fprintln() и Scan(), а пакет os — для объекта Stderr.

Внутри функции main() объявляются две целочисленные переменные x и y. При объявлении переменным сразу присваивается начальное значение (0). В следующей строке выводится подсказка для пользователя ("x = "). Благодаря этой подсказке пользователь будет знать, что от него требуется. После ввода числа и нажатия клавиши <Enter> значение будет присвоено переменной x. Ввод значения производится с помощью функции Scan(). В качестве значения мы передаем адрес переменной x (fmt.Scan(&x)). Значения, возвращаемые функцией Scan(), мы присваиваем переменным. Чтобы обработать ошибку достаточно получить только второе значение, поэтому первое значение сохраняется в специальной переменной, название которой содержит один символ подчеркивания. Тем самым мы игнорируем первое возвращаемое значение.

Далее с помощью оператора ветвления if мы проверяем наличие ошибки (err != nil). Если переменная err не содержит значение nil, то при вводе числа возникла ошибка. В этом случае с помощью функции Fprintln() выводим сообщение об ошибке. Обратите внимание: сообщение записывается в поток вывода сообщений об ошибках, а не в стандартный поток вывода. В следующей инструкции вызывается оператор return. Оператор прекращает выполнение текущей функции и возвращает указанное значение (если оно есть). Учитывая, что оператор return вызывается внутри функции main(), то прекращается выполнение всей программы.

Далее производится вывод подсказки и получение второго числа, которое сохраняется в переменной y. Обратите внимание: значения, возвращаемые функцией Scan(), сохраняются в переменных с помощью оператора =, а не оператора :=. В одной области видимости нельзя объявить две одноименные переменные. Переменная err у нас уже была объявлена ранее, поэтому оператор объявления := использовать больше нельзя. В следующей строке производится сложение двух чисел и вывод результата. Процесс ввода двух чисел и получения суммы выглядит так (данные, вводимые пользователем, выделены полужирным шрифтом):

C:\book\test>go run main.go
x = 10
y = 20
Сумма равна: 30

При вводе данных производится попытка преобразования к типу переменной. В нашем примере строка "10" будет преобразована в число 10, а строка "20" — в число 20. Однако, пользователь вместо числа может ввести все что угодно, например, строку не содержащую число. В этом случае преобразование закончится неудачей, переменной не будет присвоено значение, а введенное значение останется в буфере и будет автоматически считано следующей операцией ввода. Ошибку ввода мы обработали, поэтому ошибочный ввод будет выглядеть так:

C:\book\test>go run main.go
x = строка
Вы ввели не число

C:\book\test>go run main.go
x = 10
y = строка
Вы ввели не число

Если пользователь при первом вводе введет два числа через пробел, то второе значение будет получено из потока сразу после вывода второй подсказки:

C:\book\test>go run main.go
x = 10 20
y = Сумма равна: 30

Немного не красиво, но результат мы получили правильный.

Вместо функции Scan() можно воспользоваться функцией Scanln() из пакета fmt. Формат функции:

fmt.Scanln(a ...interface{}) (n int, err error)

Функция принимает один или несколько параметров через запятую. В качестве значения нужно передать адрес переменной, в которую будет записано прочитанное значение. Чтобы передать адрес следует перед названием переменной добавить символ &. Функция Scanln() возвращает два значения. Первое значение содержит количество успешных записей. Через второе значение можно получить сообщение об ошибке. Если ошибка не возникла, то значением будет nil.

Функция Scanln() ожидает, что после последнего элемента будет символ перевода строки или конец потока. Если это не так, то возвращается ошибка. При этом количество успешных записей может быть не равно 0. Давайте в листинге 1.3 заменим функцию Scan() функцией Scanln() и посмотрим на результат:

C:\book\test>go run main.go
x = 10
y = 20
Сумма равна: 30

C:\book\test>go run main.go
x = строка
Вы ввели не число

C:\book\test>go run main.go
x = 10
y = строка
Вы ввели не число

C:\book\test>go run main.go
x = 10 20
Вы ввели не число

Посмотрите на последний пример и сравните результат с результатом функции Scan(). На мой взгляд функция Scanln() позволяет лучше контролировать пользовательский ввод. Но это только в том случае, если производится обработка ошибок. Если ошибку не обработать, то возможен такой результат:

s1, s2 := "", ""
fmt.Print("s1 = ")
fmt.Scanln(&s1)
fmt.Print("s2 = ")
fmt.Scanln(&s2)
fmt.Println(s1)
fmt.Println(s2)

В этом примере мы получаем две строки. Запустим программу на выполнение:

C:\book\test>go run main.go
s1 = привет
s2 = мир
привет
мир

Если вводить только одно слово, то все в порядке. Теперь попробуем ввести сразу два слова через пробел:

C:\book\test>go run main.go
s1 = привет мир
s2 = привет
ир

Первое слово было успешно получено и записано в переменную s1. Но в данном случае функция вернет также ошибку, т. к. после слова нет символа перевода строки. Данную ошибку мы не обрабатываем. В результате после считывания второго слова потеряли первую букву в слове мир и получили фрагмент ир. Что уже является явной ошибкой.

Теперь заменим функцию Scanln() функцией Scan() и повторим эксперимент:

C:\book\test>go run main.go
s1 = привет
s2 = мир
привет
мир

C:\book\test>go run main.go
s1 = привет мир
s2 = привет
мир

Немного не красиво, но результат мы получили правильный.

Из этих примеров можно вывести еще один вывод. Функции Scan() и Scanln(), а также функция Scanf(), позволяют получить только слово до первого пробела. Чтобы получить всю фразу можно воспользоваться кодом из листинга 1.4.

Листинг 1.4. Получение фраз

package main

import (
   "bufio"
   "fmt"
   "os"
)

func main() {
   txt := ""
   fmt.Println("Введите текст:")
   scanner := bufio.NewScanner(os.Stdin)
   if scanner.Scan() {
      txt = scanner.Text()
      if err := scanner.Err(); err != nil {
         fmt.Fprintln(os.Stderr, err)
         return
      }
   } else {
      fmt.Fprintln(os.Stderr, "Возникла ошибка")
      return
   }
   fmt.Println("Вы ввели:", txt)
}

Запустим код на выполнение:

C:\book\test>go run main.go
Введите текст:
привет мир
Вы ввели: привет мир

Итак, с помощью функции NewScanner() из пакета bufio мы создаем объект сканера и сохраняем его в переменной scanner. В качестве параметра функция NewScanner() принимает объект потока, из которого будет производиться считывание. В качестве потока может выступать файл или стандартный поток ввода (os.Stdin), связанный с окном консоли. Что такое объект? В языке Go объект является экземпляром какой-либо структуры. Структура может содержать поля (переменные внутри структуры) и методы (функции, связанные со структурой). Для получения доступа к полям и методам используется точечная нотация. Вначале указывается объект, затем ставится точка, после которой идет название поля или метода (scanner.Scan()). В листинге 1.4 мы вызываем три метода объекта scanner: Scan() — для запуска сканирования, Text() — для получения строки, введенной пользователем, и Err() — для проверки наличия ошибки при выполнении операции.

Обратите внимание: мы объявляем переменную err после ключевого слова if. После объявления переменной ставится точка с запятой, а потом указывается проверяемое условие. При таком объявлении переменная err будет видна только внутри блоков оператора if:

if err := scanner.Err(); err != nil {

В листинге 1.3 при ошибке мы сразу завершаем выполнение программы не давая возможности пользователю исправить ситуацию. Это неправильно. Представьте, пользователь заполняет правильно 50 полей, а на 51-ом поле случайно ошибается. Мы завершаем программу и пользователю придется заново заполнять 50 полей. Давайте это исправим и дадим пользователю три попытки ввода, прежде чем досрочно завершим программу (листинг 1.5).

Листинг 1.5. Обработка ошибок ввода

package main

import (
   "bufio"
   "fmt"
   "os"
)

func main() {
   x, y, count := 0, 0, 1
   // Получаем первое число
   for {                        // Бесконечный цикл
      fmt.Print("x = ")
      _, err := fmt.Scanln(&x)
      if err != nil {           // Проверяем успешность ввода
         fmt.Fprintln(os.Stderr, "Вы ввели не число")
         count++
         if count > 3 {
            fmt.Fprintln(os.Stderr, "Вы сделали три ошибки")
            return              // Завершаем программу
         }
         // Очищаем поток
         scanner := bufio.NewScanner(os.Stdin)
         scanner.Scan()
      } else {
         break                  // Выходим из цикла
      }
   }
   // Получаем второе число
   count = 1
   for {                        // Бесконечный цикл
      fmt.Print("y = ")
      _, err := fmt.Scanln(&y)
      if err != nil {           // Проверяем успешность ввода
         fmt.Fprintln(os.Stderr, "Вы ввели не число")
         count++
         if count > 3 {
            fmt.Fprintln(os.Stderr, "Вы сделали три ошибки")
            return              // Завершаем программу
         }
         // Очищаем поток
         scanner := bufio.NewScanner(os.Stdin)
         scanner.Scan()
      } else {
         break                  // Выходим из цикла
      }
   }
   // Выводим результат
   fmt.Println("Сумма равна:", x + y)
}

В этом примере внутри функции main() объявляются три целочисленные локальные переменные: x, y и count. В переменных x и y мы будем сохранять введенные пользователем значения, а в переменной count — количество допущенных ошибок подряд.

При возникновении ошибки ее необходимо обработать, а затем вывести повторный запрос на ввод числа. Вполне возможно эту процедуру нужно будет выполнить несколько раз. Причем количество повторов заранее неизвестно. Для выполнения одних и тех же инструкций несколько раз предназначены циклы. В нашем примере используется цикл for. Учитывая, что после ключевого слова for нет условия, инструкции внутри фигурных скобок будут выполняться до тех пор, пока мы вручную не прервем цикл с помощью оператора break или вызова инструкции return, которая завершит также выполнение функции main(). Если цикл не прервать, то инструкции будут выполняться бесконечно.

Внутри цикла выводим подсказку пользователю с помощью функции Print(), а затем получаем значение с помощью функции Scanln(). Далее проверяем наличие ошибки. Если произошла ошибка, то выводим сообщение Вы ввели не число. Количество попыток мы отслеживаем с помощью переменной count. На начальном этапе она имеет значение 1. Если пользователь не ввел число, то увеличиваем значение на 1 с помощью инструкции:

count++

Если пользователь допустил три ошибки подряд (переменная count будет содержать значение больше 3), то выводим сообщение Вы сделали три ошибки и выходим из функции main(), вызвав оператор return.

После неправильного ввода ошибочное значение по-прежнему будет находится в потоке. Если поток не очистить, то значение будет автоматически передано следующей операции ввода, что породит повторную ошибку. Проигнорировать значение, расположенное в потоке, позволяют следующие инструкции:

scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()

После очистки потока управление будет передано в начало цикла и процесс ввода числа повторится.

Если ошибки нет, выполняются инструкции, расположенные после ключевого слова else. В нашем случае вызывается оператор break, который прерывает выполнение цикла.

Далее таким же способом получаем второе число. Так как в предыдущем цикле значение переменной count было изменено, перед циклом производим восстановление первоначального значения. Если второе число успешно получено, производим вывод суммы чисел.

Помимо функций Scan() и Scanln() в пакете fmt определена функция Scanf(), которая позволяет указать формат вводимых данных. Формат функции:

fmt.Scanf(format string, a ...interface{}) (n int, err error)

Функция принимает строку формата и один или несколько параметров через запятую. Внутри строки формата можно указать обычные символы и спецификаторы формата, начинающиеся с символа % (см. описание функции Printf() в разд. 1.9). Количество спецификаторов должно совпадать с количеством переданных параметров. В качестве значения нужно передать адрес переменной, в которую будет записано прочитанное значение. Чтобы передать адрес следует перед названием переменной добавить символ &. Функция Scanf() возвращает два значения. Первое значение содержит количество успешных записей. Через второе значение можно получить сообщение об ошибке. Если ошибка не возникла, то значением будет nil.

Пример получения целочисленного значения:

x := 0
fmt.Print("x = ")
n, err := fmt.Scanf("%d", &x)
fmt.Println(n, err)
fmt.Println("Значение x =", x)

Учебник Go (Golang)
Учебник Go (Golang) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов

cpp