Строка в число: 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. Здесь первый результат — число, второй — ошибка.
- Строка похожа на целое число:
qty— это число,err—nil.nilзначит «ничего», ошибки нет;Printlnпечатает его как<nil>. - Строка не число:
qty—0, а вerrлежит описание проблемы:strconv.Atoi: parsing "abc": invalid syntax. Дословно: «функция strconv.Atoi, разбирая строку "abc", нашла недопустимую запись».
Проверить, была ли ошибка, можно сравнением: 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.gopackage 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: err2. Второе — следствие первого: переменной
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