День 19 · Строки, руны и файлы · страница 1 из 6

Строка — это байты

Выгрузка заказов со склада — текст: русские названия, числа, точки с запятой. Сегодня программа прочитает его из файла и напечатает ровный отчёт. Первая ловушка — длина строки: len считает не буквы.


Колонка разъехалась

Ситуация. Названия товаров нужно напечатать колонкой: короткое название дополнить точками до двенадцати знаков и поставить черту. Длину названия берём через len — тем же len, которым в день 16 мерили срез.

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

import (
	"fmt"
	"strings"
)

func main() {
	width := 12
	names := []string{"Bolt-M8", "Болт М8", "Шайба"}
	for _, name := range names {
		dots := strings.Repeat(".", width-len(name))
		fmt.Println(name + dots + "|")
	}
}

strings.Repeat(".", n) возвращает строку из n точек.

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day19/scratch$ go run .
Bolt-M8.....|
Болт М8|
Шайба..|

Как это читать. У латинского Bolt-M8 семь знаков и пять точек — черта на месте. У русского Болт М8 знаков тоже семь, а точек ни одной, у Шайба пять букв и две точки. Ошибка не в арифметике: len вернул не то число, которого мы ждали.


len считает байты

Дальше показываем только строки из тела main — вставляйте их между { и } в scratch/main.go. С одним import "fmt" первая строка тела — шестая.

Код для чтения · разбираем, набирать не нужно
	fmt.Println(len("Bolt"))
	fmt.Println(len("Болт"))
	fmt.Println([]byte("Bolt"))
	fmt.Println([]byte("Болт"))

Что увидите.

Вывод · это печатает программа, набирать не нужно
4
8
[66 111 108 116]
[208 145 208 190 208 187 209 130]

Как это читать. Компьютер хранит текст числами. Одно такое число — байт: от 0 до 255. []byte("Болт") показывает, из каких байтов состоит строка: это срез байтов, как срез чисел из дня 16.

Латинской букве хватает одного байта: B — 66, o — 111. Русских букв в 256 значений не уместить, поэтому Go записывает строки в кодировке UTF-8: латиница — один байт, русская буква — два. Б — это пара 208 145, о — пара 208 190. Восемь байтов, четыре буквы.

len у строки — число байтов, не букв. Для латиницы эти числа совпадают, поэтому ошибка годами живёт в программах, проверенных на английских названиях.

Сколько байтов у знаков, которые встречаются в выгрузках:

Код для чтения · разбираем, набирать не нужно
	fmt.Println(len("ё"), len("№"), len("€"), len(" "))
Вывод · это печатает программа, набирать не нужно
2 3 3 1
Знак Байтов
латинская буква, цифра, пробел, ;, . 1
русская буква, в том числе ё 2
№, € 3

Теперь колонка из первого раздела объясняется: в Болт М8 пять русских букв по два байта и два знака по одному (пробел и 8) — len вернул 12, и на точки осталось 12 - 12 = 0.


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

Цель: увидеть, что у одной и той же строки байтов больше, чем букв, — в программе и в терминале.

1. Наберите программу и запустите:

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

import "fmt"

func main() {
	fmt.Println(len("Bolt"))
	fmt.Println(len("Болт"))
	fmt.Println([]byte("Bolt"))
	fmt.Println([]byte("Болт"))
}
▶ Выполните
cd ~/gocourse/day19/scratch
go run .

2. Терминал умеет считать то же самое. wc -c считает байты, wc -m — знаки:

▶ Выполните
printf 'Болт' | wc -c
printf 'Болт' | wc -m

Что должно получиться:

Вывод · это печатает программа, набирать не нужно
8
4

Готово, когда: программа напечатала 4 и 8, а wc — 8 и 4 (пункт проверки t_bytes).


Попробуйте сейчас: что выведет len.

Цель: предсказать длину строки с русскими буквами и сдать ответ.

1. Не запуская, посчитайте, что напечатает строка из тела main:

Код для чтения · разбираем, набирать не нужно
	fmt.Println(len("Гайка М10"))

Буква М здесь русская.

2. Сдайте число:

▶ Выполните · выделенное замените своим
course answer day19.q1 ЧИСЛО

Вместо ЧИСЛО — то, что напечатает программа.

3. Проверьте запуском в scratch.

Готово, когда: course answer ответил Принято: day19.q1 = … (пункт проверки q1).


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

Добавим в колонку из первого раздела длинное русское название — "Анкер клиновой", четырнадцать букв:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day19/scratch$ go run .
Bolt-M8.....|
Болт М8|
Шайба..|
panic: strings: negative Repeat count

goroutine 1 [running]:
strings.Repeat({0x49b000?, 0x1fccb4922008?}, 0x1fccb4876e58?)
	/usr/local/go/src/strings/strings.go:609 +0x56b
main.main()
	/home/stagiaire/gocourse/day19/scratch/main.go:12 +0xbc
exit status 2

В "Анкер клиновой" 27 байтов, 12 - 27 меньше нуля, а строку из минус пятнадцати точек strings.Repeat сделать не может — программа падает. Строка 12 — та, где считаются точки.

Что видите Что это значит Что делать
panic: strings: negative Repeat count ширину дополнения посчитали через len, а байтов в названии больше ширины считать буквы, а не байты — третья страница
колонка ровная у латинских названий и разъезжается у русских длина взята через len то же
len строки больше, чем букв в ней так и есть: русская буква — два байта для длины в буквах — третья страница
wc -c и wc -m печатают разные числа -c — байты, -m — знаки ничего, так и должно быть

Дальше: индекс строки — это номер байта, а не буквы — course next