День 07 · Ввод и первые ошибки · страница 3 из 6

Строка в число: strconv.Atoi

Программа прочитала 40. Кажется, осталось умножить на цену. Но scanner.Text() отдаёт string, а строку на число не умножить — Go не станет угадывать, что вы имели в виду. Превращать строку в число нужно явно, и у этого превращения два результата: число и ответ на вопрос «получилось ли».


Строку не умножить

Ситуация. Кладовщик вводит количество, одна штука стоит 250 копеек. Нужна сумма.

Код для чтения · разбираем, набирать не нужно
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	qty := scanner.Text()
	fmt.Println(qty * 250)
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day07/scratch$ go run .
# day07/scratch
./main.go:13:14: invalid operation: qty * 250 (mismatched types string and untyped int)

Как это читать. «Недопустимая операция: типы не совпадают — строка и целое число». Та же ошибка mismatched types, что в дне 03 при int и float64. qty — строка, потому что scanner.Text() всегда возвращает строку, даже если в ней одни цифры.

С плюсом ещё коварнее: qty + "1" компилируется, потому что + у строк — склейка. На ввод 15 такая программа печатает 151, а не 16.

Попытка объявить переменную числом тоже не пройдёт:

Вывод · это печатает программа, набирать не нужно
./main.go:12:16: cannot use scanner.Text() (value of type string) as int value in variable declaration

Это сообщение на строку var qty int = scanner.Text(): «нельзя использовать строку как значение int». Если qty объявлена раньше, а строка ей присваивается, конец сообщения другой — in assignment.


strconv.Atoi: два результата

В дне 04 был strconv.Itoa — число в строку. Обратное превращение — strconv.Atoi: ASCII to integer, «текст в целое».

Код для чтения · разбираем, набирать не нужно
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	qty, err := strconv.Atoi(scanner.Text())
	fmt.Println(qty, err)
	fmt.Println("Сумма, коп.:", qty*250)
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day07/scratch$ echo 15 | go run .
15 <nil>
Сумма, коп.: 3750
stagiaire@lab:~/gocourse/day07/scratch$ echo abc | go run .
0 strconv.Atoi: parsing "abc": invalid syntax
Сумма, коп.: 0

Как это читать. qty, err := strconv.Atoi(…) — слева две переменные, потому что функция возвращает два значения. Так уже было в дне 04 со strings.CutPrefix: результат и bool. Здесь первый результат — число, второй — ошибка.

Проверить, была ли ошибка, можно сравнением: err == nil — true, если всё прошло хорошо. Подробно про err — на следующей странице.

Второй запуск показывает, чем опасна строка Сумма, коп.: 0: программа не знает, что 0 получился из мусора, и спокойно считает дальше.


Что Atoi считает числом

Результаты strconv.Atoi на разных строках, Go 1.27:

Строка Число Ошибка
"15" 15 <nil>
"-5" -5 <nil>
"+5" 5 <nil>
"007" 7 <nil>
"abc" 0 strconv.Atoi: parsing "abc": invalid syntax
"" — пустая строка 0 strconv.Atoi: parsing "": invalid syntax
"12 " — пробел в конце 0 strconv.Atoi: parsing "12 ": invalid syntax
" 12" — пробел в начале 0 strconv.Atoi: parsing " 12": invalid syntax
"1.5" 0 strconv.Atoi: parsing "1.5": invalid syntax
"12,5" 0 strconv.Atoi: parsing "12,5": invalid syntax
"1 000" 0 strconv.Atoi: parsing "1 000": invalid syntax
"99999999999999999999" 9223372036854775807 strconv.Atoi: parsing "99999999999999999999": value out of range

Главное в таблице — строки с пробелами. Человек видит 12, а Atoi видит 12 с пробелом и отказывается. Поэтому прочитанную строку сначала пропускают через strings.TrimSpace — правило со второй страницы. Дробные числа и числа с разделителем тысяч Atoi тоже не принимает: он разбирает только знак и цифры.

Последняя строка — число, которое не помещается в int. Atoi отдаёт самое большое int, какое бывает, и ошибку value out of range — «значение вне диапазона». Число при ошибке всегда нужно выбросить, каким бы правдоподобным оно ни выглядело.


Попробуйте сейчас: что выведет программа?

Цель: предсказать, что Atoi сделает со строкой с пробелом в конце, и сдать ответ day07.q1.

1. Не запуская, прочитайте программу. На ввод ей подадут 12 — в кавычках после 12 стоит пробел:

Код для чтения · разбираем, набирать не нужно
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	qty, err := strconv.Atoi(scanner.Text())
	fmt.Println(qty+1, err == nil)
}

TrimSpace здесь нет: Text() отдаёт строку как есть. Решите, какое число попадёт в qty и чему будет равно err == nil, а потом посчитайте qty+1.

2. Отправьте число и слово true или false через пробел — в том порядке, в каком их печатает Println:

▶ Выполните · выделенное замените своим
course answer day07.q1 ВАШ_ОТВЕТ

3. После ответа проверьте себя запуском. Теперь ту же программу нужно набрать — в scratch/main.go:

✎ Наберите в файл scratch/main.go
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	qty, err := strconv.Atoi(scanner.Text())
	fmt.Println(qty+1, err == nil)
}
▶ Выполните
cd ~/gocourse/day07/scratch
echo '12 ' | go run .

Готово, когда: ответ сдан, и запуск показал то же, что вы предсказали; в course check 07 пункт «Что выведет: Atoi на строке с пробелом в конце» (q1) отмечен ✔.


Дробные числа — вес посылки, объём коробки — превращает в float64 функция strconv.ParseFloat. В заданиях её не будет: деньги в курсе в копейках, вес в граммах, всё целое, и хватает Atoi. Как устроена ParseFloat и почему она не понимает 12,5 — в course extra 07, раздел «Дробные: strconv.ParseFloat».


Что может пойти не так

Что видите Что это значит Что делать
assignment mismatch: 1 variable but strconv.Atoi returns 2 values слева одна переменная, а Atoi возвращает два значения qty, err := strconv.Atoi(…)
declared and not used: err ошибку получили, но ни разу не посмотрели проверить err — как, на следующей странице
invalid operation: qty * 250 (mismatched types string and untyped int) qty — строка из Text() превратить через strconv.Atoi
cannot use scanner.Text() (value of type string) as int value in variable declaration строку кладут в переменную int strconv.Atoi
на ввод 15 программа напечатала 151 + склеил строки сначала Atoi, потом сложение
strconv.Atoi: parsing "12 ": invalid syntax, хотя ввод «правильный» пробел или табуляция по краям strings.TrimSpace перед Atoi
undefined: strconv пакет не подключён "strconv" в import

Попробуйте сейчас: починка fix2.

Цель: fix2 собирается, на 1990 печатает цену в рублях, на не число сообщает об ошибке.

1. Программа fix2 не собирается. Запустите:

▶ Выполните
cd ~/gocourse/day07/fix2
go run .

Компилятор выдаст два сообщения:

Вывод · это печатает программа, набирать не нужно
# day07/fix2
./main.go:17:11: assignment mismatch: 1 variable but strconv.Atoi returns 2 values
./main.go:18:5: undefined: err

2. Второе — следствие первого: переменной err нет, потому что в строке 17 для неё не нашлось места. Здесь хватает одной строки; проверка принимает не больше двух, строки import не считаются — тот же лимит записан в course lab 07. Лимит — часть пункта fix2: программу, переписанную заново, пункт не засчитает, даже если она печатает всё правильно. Строки с os.Stderr и os.Exit пока читайте как «сообщить об ошибке и выйти» — разберём на пятой странице.

Частые промахи: price, err = … со знаком = даёт четыре сообщения undefined — переменных ещё нет, их надо объявить через :=. price, _ := … убирает первое сообщение, но оставляет undefined: err.

3. Когда соберётся, проверьте оба случая из комментария в начале файла:

▶ Выполните
echo 1990 | go run .
echo abc | go run .

Готово, когда: на 1990 программа печатает Цена: 19 руб. 90 коп., на abc — сообщение об ошибке и exit status 1 вместо цены, а изменённых строк не больше двух (пункт проверки fix2).


Дальше: ошибка — это значение — course next