Показаны сообщения с ярлыком строковый тип в Go. Показать все сообщения
Показаны сообщения с ярлыком строковый тип в Go. Показать все сообщения

пятница, 18 октября 2024 г.

Как итерировать по символам в строках Go

В Go итерация по строке по ее символам (или знакам) может быть выполнена несколькими различными способами.

Использование цикла for...range

Наиболее распространенный и идиоматический способ итерации по строке в Go — использование цикла for...range. Этот метод правильно обрабатывает символы Unicode, рассматривая каждый символ как руну (что является псевдонимом для int32). Вот пример:

package main

import "fmt"

func main() {
    str := "Hello, 世界" // Строка с символами ASCII и не-ASCII

    for index, char := range str {
        fmt.Printf("Index: %d, Character: %c\n", index, char)
    }
}

Вывод:

Index: 0, Character: H
Index: 1, Character: e
Index: 2, Character: l
Index: 3, Character: l
Index: 4, Character: o
Index: 5, Character: ,
Index: 6, Character:
Index: 7, Character: 世
Index: 8, Character: 界

Использование стандартного цикла for с рунами

Если вы предпочитаете использовать традиционный цикл for, вы можете преобразовать строку в срез рун. Это позволяет вам перебирать каждый символ, сохраняя правильное представление символов Unicode:

package main

import "fmt"

func main() {
    str := "Hello, 世界"
    runes := []rune(str) // Преобразовать строку в срез рун

    for i := 0; i < len(runes); i++ {
        fmt.Printf("Rune %d is '%c'\n", i, runes[i])
    }
}

Вывод:

Rune 0 is 'H'
Rune 1 is 'e'
Rune 2 is 'l'
Rune 3 is 'l'
Rune 4 is 'o'
Rune 5 is ','
Rune 6 is ' '
Rune 7 is '世'
Rune 8 is '界'

Использование метода strings.Split

Другой подход — использовать метод strings.Split для разделения строки на отдельные символы. Однако этот метод может быть не таким эффективным или идиоматичным, как предыдущие методы:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, 世界"
    chars := strings.Split(str, "") // Разделить строку на отдельные символы

    for _, char := range chars {
        fmt.Printf("Character: %s\n", char)
    }
}

Вывод:

Character: H
Character: e
Character: l
Character: l
Character: o
Character: ,
Character:
Character: 世
Character: 界

В Go предпочтительным способом перебора строк по символам является использование цикла for...range, поскольку он автоматически обрабатывает кодировку UTF-8 и обеспечивает простой способ доступа к каждому символу как к руне. В качестве альтернативы преобразование строки в срез рун обеспечивает больший контроль с помощью традиционной индексации. Метод strings.Split также можно использовать, но он менее эффективен для этой цели. Понимание этих методов поможет вам эффективно работать со строками в Go.


Читайте также:


Строки как тип данных в Go

В Go (Golang) строка — это фундаментальный тип данных, используемый для представления последовательности символов. Он широко используется для обработки текста и необходим для различных задач программирования.

Характеристики строк в Go

Неизменяемость - после создания строки ее значение не может быть изменено. Любая операция, которая, как кажется, изменяет строку, на самом деле создает новую строку. Эта неизменяемость помогает оптимизировать использование памяти и обеспечить безопасность потоков.

Кодировка UTF-8 - строки в Go по умолчанию кодируются в UTF-8, что позволяет им представлять широкий спектр символов из разных языков. Каждый символ может занимать один или несколько байтов, что делает его гибким для интернационализации.

Срез байтов только для чтения - строку можно рассматривать как срез байтов только для чтения. Это означает, что хотя вы можете получить доступ к байтам, составляющим строку, вы не можете изменять их напрямую.

Создание строк

Строки в Go можно создавать с помощью двойных кавычек для интерпретируемых литералов или обратных кавычек для необработанных литералов.

Интерпретируемые литералы - поддерживают escape-последовательности (например, "\n" для новой строки).

str1 := "Hello, World!"

Необработанные литералы - заключены в обратные кавычки, они не поддерживают escape-последовательности и могут охватывать несколько строк.

str2 := `Это необработанный строковый литерал,
который может охватывать несколько строк.`

Операции и функции со строками

Go предоставляет несколько встроенных функций и методов для работы со строками, в основном через пакет strings. Некоторые распространенные операции включают:

Длина - используйте len() для получения количества байтов в строке.

length := len(str1) // Возвращает длину str1

Конкатенация - строки можно объединять с помощью оператора +.

combined := str1 + " " + str2

Подстрока - можно извлекать подстроки с помощью синтаксиса среза.

sub := str1[0:5] // "Hello"

Поиск и замена - функции, такие как strings.Contains(), strings.Replace() и strings.Index(), помогают находить подстроки или заменять части строк.

Пример кода

Вот простой пример, демонстрирующий создание строки и некоторые базовые операции:

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Создание строк
    greeting := "Hello"
    name := "World"

    // Объединение строк
    message := greeting + ", " + name + "!"
    fmt.Println(message) // Вывод: Hello, World!

    // Проверка длины
    fmt.Println("Длина:", len(message)) // Вывод: Длина: 13

    // Поиск подстроки
    index := strings.Index(message, "World")
    fmt.Println("Индекс 'World':", index) // Вывод: Индекс 'World': 7

    // Замена подстроки
    newMessage := strings.Replace(message, "World", "Gopher", 1)
    fmt.Println(newMessage) // Вывод: Hello, Gopher!
}

Строки в Go — мощный, но простой в использовании инструмент, предоставляющий основные функции для обработки текста, обеспечивая при этом эффективность за счет неизменяемости и кодировки UTF-8. Понимание того, как эффективно работать со строками, имеет решающее значение для любого разработчика Go, поскольку они составляют основу большинства приложений, работающих с текстовыми данными.


Читайте также:


воскресенье, 3 января 2021 г.

Go style guides: форматировать строки вне Printf

Если вы объявляете строки формата для функций в стиле Printf вне строкового литерала, сделайте их константными значениями.

Это помогает провести статический анализ строки формата.

Менее удачный вариант:

msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

Более удачный вариант:

const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

Именование функций в стиле Printf

Когда вы объявляете функцию в стиле Printf, убедитесь, что go vet может ее обнаружить и проверить строку формата.

Это означает, что по возможности следует использовать предопределенные имена функций в стиле Printf. go vet проверит их по умолчанию. Смотрите Printf для получения дополнительной информации.

Если использование предопределенных имен невозможно, завершите выбранное имя буквой f: Wrapf, а не Wrap. go vet можно попросить проверить конкретные имена в стиле Printf, но они должны оканчиваться на f.

$ go vet -printfuncs=wrapf,statusf


Читайте также:


среда, 20 мая 2020 г.

Необычный Trim в Golang

Что случилось со strings.TrimRight?

fmt.Println(strings.TrimRight("ABBA", "BA")) // Вывод: ""

Ответ

Функции Trim, TrimLeft и TrimRight удаляют все кодовые точки Unicode, содержащиеся в наборе. В этом случае все конечные A и B удаляются из строки, оставляя пустую строку.

Чтобы удалить заднюю строку, используйте strings.TrimSuffix.

fmt.Println(strings.TrimSuffix("ABBA", "BA")) // Вывод: "AB"


Читайте также:


купить игрушку gopher

вторник, 19 мая 2020 г.

Как складываются символы в Golang

Почему эти вызовы печати не дают тот же результат?

fmt.Println("H" + "i")
fmt.Println('H' + 'i')

Hi
177

Ответ

Литералы рун 'H' и 'i' являются целочисленными значениями, определяющими кодовые точки Unicode: 'H' - 72, 'i' - 105.

Вы можете превратить кодовую точку в строку с преобразованием.

fmt.Println(string(72) + string('i')) // "Hi"

Вы также можете использовать функцию fmt.Sprintf.

s := fmt.Sprintf("%c%c, world!", 72, 'i')
fmt.Println(s)// "Hi, world!"

Этот пост содержит список наиболее распространенных глаголов и флагов форматирования.


Читайте также:


Неизменяемые строки в Golang

Почему этот код не компилируется?

s := "hello"
s[0] = 'H'
fmt.Println(s)

../main.go:3:7: cannot assign to s[0]

Ответ

Строки Go являются неизменяемыми и ведут себя как байтовые срезы только для чтения (с несколькими дополнительными свойствами).

Чтобы обновить данные, используйте взамен срез рун.

buf := []rune("hello")
buf[0] = 'H'
s := string(buf)
fmt.Println(s)  // "Hello"

Если строка содержит только символы ASCII, вы также можете использовать байтовый срез, поскольку каждый ASCII символ занимает только 1 байт.


Читайте также:


понедельник, 6 апреля 2020 г.

Преобразовать интерфейс в строку в Golang

Используйте fmt.Sprintf для преобразования значения интерфейса в строку.

var x interface{} = "abc"
str := fmt.Sprintf("%v", x)

Фактически, та же самая техника может использоваться, чтобы получить строковое представление любой структуры данных.

var x interface{} = []int{1, 2, 3}
str := fmt.Sprintf("%v", x)
fmt.Println(str) // "[1 2 3]"


Читайте также:


воскресенье, 5 апреля 2020 г.

Преобразования между int, int64 и string в Golang

Конвертировать int/int64 в string

Используйте strconv.Itoa для преобразования int в десятичную строку.

s := strconv.Itoa(97) // s == "97"

Предупреждение: в простом преобразовании значение интерпретируется как кодовая точка Unicode, и получающаяся строка будет содержать символ, представленный этой кодовой точкой, закодированный в UTF-8.

s := string(97) // s == "a"

Используйте strconv.FormatInt для форматирования int64 по заданному основанию.

var n int64 = 97
s := strconv.FormatInt(n, 10) 
// s == "97" (десятичный)

var n int64 = 97
s := strconv.FormatInt(n, 16) 
// s == "61" (шестнадцатеричный)

Конвертировать string в int/int64

Используйте strconv.Atoi для парсинга десятичной строки в int.

s := "97"
if n, err := strconv.Atoi(s); err == nil {
    fmt.Println(n+1)
} else {
    fmt.Println(s, " не является целым числом.")
}
// Вывод: 98

Используйте strconv.ParseInt, чтобы парсить десятичную строку (основание 10) и проверить, соответствует ли она int64.

s := "97"
n, err := strconv.ParseInt(s, 10, 64)
if err == nil {
    fmt.Printf("%d of type %T", n, n)
}
// Вывод: 97 of type int64

Два числовых аргумента представляют собой основание (0, 2 до 36) и размер в битах (от 0 до 64).

Если первый аргумент равен 0, основание подразумевается префиксом строки: основание 16 для "0x", основание 8 для "0" и основание 10 в противном случае.

Второй аргумент указывает целочисленный тип, в который должен вписываться результат. Размеры битов 0, 8, 16, 32 и 64 соответствуют int, int8, int16, int32 и int64.

Конвертировать int в int64 (и обратно)

Размер int зависит от реализации, он составляет 32 или 64 бита, и, следовательно, вы не потеряете никакой информации при преобразовании из int в int64.

var n int = 97
m := int64(n) // безопасно

Однако при преобразовании в более короткий целочисленный тип значение усекается, чтобы соответствовать размеру типа результата.

var m int64 = 2 << 32
n := int(m)    
// усекается на машинах с 32-битными целыми числами

fmt.Println(n) // 0 или 4294967296

Общее форматирование (ширина, отступ, знак)

Функция fmt.Sprintf - это полезный общий инструмент для преобразования данных в строку:

s := fmt.Sprintf("%+8d", 97)
// s == "     +97" 
// (ширина 8, выравнивание по правому краю, 
// всегда показывать знак)


Читайте также:


суббота, 4 апреля 2020 г.

Преобразования между float и string в Golang

Конвертировать string (строку) в float

Используйте функцию strconv.ParseFloat для анализа строки как числа с плавающей запятой с точностью, указанной в bitSize: 32 для float32 или 64 для float64.

func ParseFloat(s string, bitSize int) (float64, error)

Когда bitSize равен 32, результат все еще имеет тип float64, но он будет преобразован в float32 без изменения его значения.

f := "3.14159265"
if s, err := strconv.ParseFloat(f, 32); err == nil {
    fmt.Println(s) // 3.1415927410125732
}
if s, err := strconv.ParseFloat(f, 64); err == nil {
    fmt.Println(s) // 3.14159265
}

Конвертировать float в string (строку)

Используйте метод fmt.Sprintf для форматирования числа с плавающей запятой в виде строки.

s := fmt.Sprintf("%f", 123.456) // s == "123.456000"

Форматирование Описание Глагол
1.234560e+02 Научная запись %e
123.456000 Десятичная дробь, без экспоненты %f
123.46 Десятичная дробь, точность 2 %.2f
␣␣123.46 Ширина 8, точность 2 %8.2f
123.456 Экспонент по мере необходимости, только необходимые цифры %g

Читайте также:


Преобразование между массивом/срезом рун и строкой в Golang

Конвертировать строку в руны

Когда вы преобразуете строку в срез рун, вы получаете новый срез, который содержит кодовые точки Unicode (руны) строки.

Для недопустимой последовательности UTF-8 значение руны будет 0xFFFD для каждого недопустимого байта.

r := []rune("ABC€")
fmt.Println(r)        // [65 66 67 8364]
fmt.Printf("%U\n", r) // [U+0041 U+0042 U+0043 U+20AC]

Вы также можете использовать range цикл для доступа к кодовым точкам строки.

r := "ABC€"
for _, value := range r {
 fmt.Println(value)
 fmt.Printf("%U\n", value)
}

Вывод:

65
U+0041
66
U+0042
67
U+0043
8364
U+20AC

Конвертировать руны в строку

Когда вы конвертируете срез рун в строку, вы получаете новую строку, которая представляет собой конкатенацию рун, преобразованных в строки в кодировке UTF-8.

Значения вне диапазона допустимых кодовых точек Unicode преобразуются в \uFFFD, символ замены Unicode �.

s := string([]rune{'\u0041', '\u0042', '\u0043', '\u20AC', -1})
fmt.Println(s) // ABC€�

Производительность

Эти преобразования создают новый срез или строку и, следовательно, имеют временную сложность, пропорциональную количеству обрабатываемых байтов.

Более эффективная альтернатива в некоторых случаях - использовать strings.Builder, который может объединять строки без избыточного копирования.


Читайте также:


пятница, 3 апреля 2020 г.

Преобразование между массивом/срезом байтов и строкой в Golang

Когда вы конвертируете между строкой и срезом (массивом) байтов, вы получаете совершенно новый срез, который содержит те же байты, что и строка, и наоборот.

  • Преобразование не меняет данные;
  • единственное отличие состоит в том, что строки являются неизменяемыми, а срезы байтов могут быть изменены.

Если вам нужно манипулировать символами (рунами) строки, вы можете вместо этого преобразовать строку в срез рун.

Конвертировать строку в байты

Когда вы преобразуете строку в срез байтов, вы получаете новый срез, который содержит те же байты, что и строка.

b := []byte("ABC€")
fmt.Println(b) // [65 66 67 226 130 172]

Обратите внимание, что символ € кодируется в UTF-8 с использованием 3 байтов.

Конвертировать байты в строку

Когда вы конвертируете срез байтов в строку, вы получаете новую строку, которая содержит те же байты, что и срез.

s := string([]byte{65, 66, 67, 226, 130, 172})
fmt.Println(s) // ABC€

Производительность

Эти преобразования создают новый срез или строку и, следовательно, имеют временную сложность, пропорциональную количеству обрабатываемых байтов.

Более эффективная альтернатива в некоторых случаях - использовать построитель строк (strings.Builder), который может объединять строки без избыточного копирования.


Читайте также:


Три способа разделить строку на срез в Golang

Разделить по запятой или другой подстроке

Используйте функцию strings.Split, чтобы разбить строку на значения, разделенные запятыми.

s := strings.Split("a,b,c", ",")
fmt.Println(s)
// Вывод: [a b c]

Чтобы включить разделители, используйте strings.SplitAfter. Чтобы разделить только первые n значений, используйте strings.SplitN и strings.SplitAfterN.

Вы можете использовать strings.TrimSpace для удаления начальных и конечных пробелов из результирующих строк.

Разделить по пробелам и символам новой строки

Используйте функцию strings.Fields, чтобы разбить строку на подстроки, удаляя любые пробелы, включая символы новой строки.

s := strings.Fields(" a \t b \n")
fmt.Println(s)
// Вывод: [a b]

Разделить по регулярному выражению

В более сложных ситуациях метод regexp Split может помочь.

Он разбивает строку на подстроки, разделенные регулярным выражением. Метод принимает целочисленный аргумент n; если n >= 0, он возвращает не более n подстрок.

a := regexp.MustCompile(`a`)              // единичный `a`
fmt.Printf("%q\n", a.Split("banana", -1)) // ["b" "n" "n" ""]
fmt.Printf("%q\n", a.Split("banana", 0))  // [] (nil slice)
fmt.Printf("%q\n", a.Split("banana", 1))  // ["banana"]
fmt.Printf("%q\n", a.Split("banana", 2))  // ["b" "nana"]

zp := regexp.MustCompile(` *, *`)             // пробелы и одна запятая
fmt.Printf("%q\n", zp.Split("a,b ,  c ", -1)) // ["a" "b" "c "]


Читайте также:


четверг, 12 марта 2020 г.

Руны и кодировка символов в Golang

Символы, ASCII и Unicode

Тип rune является псевдонимом для int32 и используется, чтобы подчеркнуть, что целое число представляет кодовую точку.

ASCII определяет 128 символов, обозначенных кодовыми точками 0–127. Он охватывает английские буквы, латинские цифры и несколько других символов.

Unicode, который является надмножеством ASCII, определяет кодовое пространство из 1114112 кодовых точек. Unicode версия 10.0 охватывает 139 современных и исторических скриптов (включая рунический алфавит, но не клингон), а также множество наборов символов.

Строки и кодировка UTF-8

Строка - это последовательность байтов, а не рун.

Однако строки часто содержат текст Unicode, закодированный в UTF-8, который кодирует все кодовые точки Unicode, используя от одного до четырех байтов. (Символы ASCII кодируются одним байтом, в то время как другие кодовые точки используют больше.)

Поскольку сам исходный код Go кодируется как UTF-8, строковые литералы автоматически получат эту кодировку.

Например, в строке "café" символ é (кодовая точка 233) кодируется с использованием двух байтов, тогда как символы ASCII c, a и f (кодовые точки 99, 97 и 102) используют только один:

fmt.Println([]byte("café")) // [99 97 102 195 169]
fmt.Println([]rune("café")) // [99 97 102 233]


Читайте также:


Купить gopher

вторник, 10 марта 2020 г.

Обзор обработки строк в Golang

Строковые литералы (экранированные символы)

Выражение Результат Описание
"" Нулевое значение по умолчанию для типа string
"Japan 日本"
Japan 日本
Код Go это Unicode текст, кодированный в UTF‑8
"\xe6\x97\xa5" \xNN определяет byte
"\u65E5" \uNNNN определяет Unicode значение
"\\" \ Обратный слэш
"\"" " Двойная кавычка
"\n" Новая строка
"\t" Символ табуляции
`\xe6` \xe6 Необработанный строковый литерал
html.EscapeString("<>")
&lt;&gt; HTML экранирование для <, >, &, ' и "
url.PathEscape("A B")
A%20B URL %-кодирование net/url

В необработанных строковых литералах (заключены в обратные кавычки ``) текст интерпретируется буквально и обратные слэши не имеют специального значения.

Конкатенация

Выражение Результат Описание
"Ja" + "pan" Japan Конкатенация

Эквивалетность и сравнение (игнорируя регистр)

Выражение
Результат
Описание
"Japan" == "Japan" true Эквивалетность
strings.EqualFold("Japan", "JAPAN") true Сравнение Unicode с оберткой регистра
"Japan" < "japan" true Сравнение в лексикографическом порядке

Длина в байтах или рунах

Выражение
Результат
Описание
len("日") 3 Длина в байтах
utf8.RuneCountInString("日")
1 Длина в рунах unicode/utf8
utf8.ValidString("日") true Проверка UTF-8 ли переданная строка (unicode/utf8)

Индексирование, создание подстроки, итерация

Выражение Результат Описание
"Japan"[2] 'p' Байт в позиции 2
"Japan"[1:3] ap Индексирование байтов
"Japan"[:2] Ja
"Japan"[2:] pan

Цикл range в Go производит итерацию по UTF-8-кодированным символам (руны):

for i, ch := range "Japan 日本" {
    fmt.Printf("%d:%q ", i, ch)
}
// Вывод: 0:'J' 1:'a' 2:'p' 3:'a' 4:'n' 5:' ' 6:'日' 9:'本'

Итерация по байтам производит бессмысленные символы для не-ASCII текста:

s := "Japan 日本"
for i := 0; i < len(s); i++ {
    fmt.Printf("%q ", s[i])
}
// Вывод: 'J' 'a' 'p' 'a' 'n' ' ' 'æ' '\u0097' '¥' 'æ' '\u009c' '¬'

Поиск (contains, prefix/suffix, index)

Выражение
Результат
Описание
strings.Contains("Japan", "abc") false abc в Japan?
strings.ContainsAny("Japan", "abc") true a, b или c в Japan?
strings.Count("Banana", "ana") 1 не перекрывающиеся экземпляры ana
strings.HasPrefix("Japan", "Ja") true Japan начинается с Ja?
strings.HasSuffix("Japan", "pan") true Japan заканчивается на pan?
strings.Index("Japan", "abc") -1 Индекс первого abc
strings.IndexAny("Japan", "abc") 1 Индекс первого a, b или c
strings.LastIndex("Japan", "abc") -1 Индекс последнего abc
strings.LastIndexAny("Japan", "abc") 3 Индекс последнего a, b или c

Замена (uppercase/lowercase, trim)

Выражение
Результат
Описание
strings.Replace("foo", "o", ".", 2) f.. Замена первых двух “o” на “.”. Используйте -1, чтобы заменить все подходящие символы.

f := func(r rune) rune {
    return r + 1
}
strings.Map(f, "ab")

bc Применяет функцию к каждому символу
strings.ToUpper("Japan") JAPAN Приведение к верхнему регистру
strings.ToLower("Japan") japan Приведение к нижнему регистру
strings.Title("ja pan")
Ja Pan
Приведение к верхнему регистру первых букв
strings.TrimSpace(" foo\n") foo Удаление начального и завершающего пробела
strings.Trim("foo", "fo") Удаление начальных и завершающих f и s
strings.TrimLeft("foo", "f") oo Удаление начальных f
strings.TrimRight("foo", "o") f Удаление завершающих f
strings.TrimPrefix("foo", "fo") o Удаление префикса
strings.TrimSuffix("foo", "o") fo Удаление суффикса

Разделение по пробелам или запятым

Выражение Результат Описание
strings.Fields(" a\t b\n") ["a" "b"] Удаление пробелов
strings.Split("a,b", ",") ["a" "b"] Удаление разделителя
strings.SplitAfter("a,b", ",") ["a," "b"] Сохранение разделителя

Объединение строки с разделителем

Выражение Результат Описание
strings.Join([]string{"a", "b"}, ":") a:b Добавление разделителя
strings.Repeat("da", 2) dada 2 копии “da”

Форматирование и конвертирование

Выражение
Результат
Описание
strconv.Itoa(-42) "-42" Int к string
strconv.FormatInt(255, 16) "ff" Конвертирование к шестнадцетиричной основе

Sprintf

Функция fmt.Sprintf часто лучший выбор для форматирования данных:

s := fmt.Sprintf("%.4f", math.Pi) // s == "3.1416"


Читайте также:


Купить gopher

пятница, 6 марта 2020 г.

Эффективная конкатенация строк в Golang

Простое создание строк

Для простых случаев, когда производительность не является проблемой, fmt.Sprintf - ваш друг. Это чисто, просто и довольно эффективно.

s := fmt.Sprintf("Size: %d MB.", 85) // s == "Size: 85 MB."

Высокопроизводительная конкатенация строк с версии Go 1.10

strings.Builder используется для эффективного добавления строк с использованием методов записи.

  • Он предлагает подмножество методов bytes.Buffer, что позволяет безопасно избежать дополнительного копирования при преобразовании Builder в строку.
  • Вы можете использовать пакет fmt для форматирования, поскольку Builder реализует интерфейс io.Writer.
  • Метод Grow может использоваться для предварительного выделения памяти, когда известен максимальный размер строки.

var b strings.Builder
b.Grow(32)
for i, p := range []int{2, 3, 5, 7, 11, 13} {
    fmt.Fprintf(&b, "%d:%d, ", i+1, p)
}
s := b.String()   // нет копирования

s = s[:b.Len()-2] // нет копирования 
                  // (удаляет завершающий ", ")
fmt.Println(s)

Вывод:

1:2, 2:3, 3:5, 4:7, 5:11, 6:13

Создание строк до версии Go 1.10

Используйте fmt.Fprintf для печати в bytes.Buffer.

var buf bytes.Buffer
for i, p := range []int{2, 3, 5, 7, 11, 13} {
    fmt.Fprintf(&buf, "%d:%d, ", i+1, p)
}
buf.Truncate(buf.Len() - 2) // Удаляет завершающий ", "
s := buf.String()           // Копирует в новую строку
fmt.Println(s)

Вывод:

1:2, 2:3, 3:5, 4:7, 5:11, 6:13

Это решение довольно эффективно, но может привести к появлению лишнего мусора. Для более высокой производительности вы можете попробовать использовать функции добавления в пакете strconv.

buf := []byte("Size: ")
buf = strconv.AppendInt(buf, 85, 10)
buf = append(buf, " MB."...)
s := string(buf)

Если ожидаемая максимальная длина строки известна, вы можете предварительно выделить срез.

buf := make([]byte, 0, 16)
buf = append(buf, "Size: "...)
buf = strconv.AppendInt(buf, 85, 10)
buf = append(buf, " MB."...)
s := string(buf)


Читайте также:


Купить gopher

четверг, 21 ноября 2019 г.

Golang puzzlers: длина строки

Пример этого поста: определение длины строки. Дан код:

package main

import "fmt"

func main() {
  test := "frйday"
  fmt.Println(len(test))
}

Вопрос: что выведется при запуске?

На первый взгляд покажется что ответ должен быть 6, поскольку в строке 6 символов. Но при запуске в выводе будет 7!

В чем подвох? Дело в том что len измеряет строку по байтам, а не по символам. 5 символов frday являются ASCII символами и в UTF-8 (а исходный код всех строк в Go представлен UTF-8 текстом) каждый из этих символов занимает 1 байт, а символ й не является ASCII символом, и в UTF-8 занимает 2 байта, поэтому 5+2=7. Стоит сразу отметить, что исходный код строк в Go состоит из UTF-8 текста, но в UTF-8 если символ вмещается в 1 байт (как с ASCII символами), то он и записывается 1 байтом. Символ й занимает 2 байта, но если бы мы взяли какой-нибудь китайский иероглиф, который в UTF-8 занимает 3 байта, в нашем примере, то могли бы получить даже 8 в ответе, например:

package main

import "fmt"

func main() {
  test := "fr語day"
  fmt.Println(len(test))
}

Вывод:

8

Запустить пример в песочнице play.golang.org

Для более подробной информации о строках, символах и кодировке читайте этот пост.


Читайте также:


пятница, 20 сентября 2019 г.

Строка, байт, руна, символ в Golang

В этом посте обсуждаются строки в Go. Поначалу строки могут показаться слишком простой темой для поста в блоге, но для их правильного использования требуется понимание не только того, как они работают, но и разницы между байтом (byte), символом и руной (rune), разницы между Unicode и UTF-8, разницы между строкой и строковым литералом и другие различия.

Что такое строка?

Начнем с некоторых основ.

В Go строка в действительности является срезом (slice) байтов, доступным только для чтения.

Важно сразу указать, что строка содержит произвольные байты. Не требуется хранить текст Unicode, текст UTF-8 или любой другой предопределенный формат. Что касается содержимого строки, оно в точности эквивалентно срезу байтов.

Вот строковый литерал (подробнее о них ниже), который использует нотацию \xNN для определения строковой константы, содержащей некоторые специфические байтовые значения. (Конечно, байты варьируются в шестнадцатеричном представлении от 00 до FF включительно.)

const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"

Печать строк

Поскольку некоторые байты в нашем примере строки не являются допустимыми ASCII, даже не допустимыми UTF-8, прямая печать строки приведет к ужасному выводу. Простая печать

fmt.Println(sample)

выводит этот беспорядок (точный вид зависит от окружения запуска):

�� = � ⌘

Чтобы выяснить, что на самом деле содержит эта строка, нам нужно разобрать ее на части и изучить кусочки. Есть несколько способов сделать это. Наиболее очевидным является цикл по его содержимому и индивидуальное извлечение байтов, как в этом цикле for:

for i := 0; i < len(sample); i++ {
    fmt.Printf("%x ", sample[i])
}

Как и предполагалось, индексирование строки обращается к отдельным байтам, а не к символам. Мы вернемся к этой теме подробно ниже. А пока давайте придерживаться только байтов. Это вывод из байтового цикла:

bd b2 3d bc 20 e2 8c 98

Обратите внимание, как отдельные байты соответствуют шестнадцатеричным экранировкам, которые определили строку.

Более короткий способ создания презентабельного вывода для грязной строки - использовать %x (шестнадцатеричный) формат в fmt.Printf. Он просто выводит последовательные байты строки в виде шестнадцатеричных цифр, по две на байт.

fmt.Printf("%x\n", sample)

Сравните его вывод с вышеупомянутым:

bdb23dbc20e28c98

Хорошим трюком является использование "пробел" флага в этом формате, помещая пробел между % и x. Сравните используемый здесь метод форматирования строки с приведенным выше,

fmt.Printf("% x\n", sample)

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

bd b2 3d bc 20 e2 8c 98

Есть еще кое-что. Форма %q (в кавычках) будет экранировать любые непечатаемые последовательности байтов в строке, поэтому вывод будет однозначным.

fmt.Printf("%q\n", sample)

Этот метод удобен, когда большая часть строки понятна как текст, но есть особенности, которые нужно искоренить; он производит:

"\xbd\xb2=\xbc ⌘"

Если мы взглянем пристально на этот вывод, то увидим, что в шуме погребен один знак равенства ASCII вместе с обычным пробелом, и в конце появляется известный шведский символ "Достопримечательность" ("Place of Interest" symbol). Этот символ имеет значение Unicode U+2318, закодированное как UTF-8 байтами после пробела (шестнадцатеричное значение 20): e2 8c 98.

Если мы незнакомы или смущены странными значениями в строке, мы можем использовать "плюс" флаг к %q. Этот флаг заставляет выходные данные экранировать не только непечатаемые последовательности, но также и любые байты, отличные от ASCII, при интерпретации UTF-8. В результате он предоставляет Unicode значения правильно отформатированного UTF-8, который представляет не-ASCII данные в строке:

fmt.Printf("%+q\n", sample)

В этом формате Unicode значение для шведского символа отображается как \u экранированными:

"\xbd\xb2=\xbc \u2318"

Эти методы печати полезно знать при отладке содержимого строк, и они пригодятся в следующем обсуждении. Стоит также отметить, что все эти методы ведут себя точно так же для байтовых срезов, как и для строк.

Вот полный набор параметров печати, которые мы перечислили, представленные как полная программа:

package main

import "fmt"

func main() {
    const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"

    fmt.Println("Println:")
    fmt.Println(sample)

    fmt.Println("Byte loop:")
    for i := 0; i < len(sample); i++ {
        fmt.Printf("%x ", sample[i])
    }
    fmt.Printf("\n")

    fmt.Println("Printf with %x:")
    fmt.Printf("%x\n", sample)

    fmt.Println("Printf with % x:")
    fmt.Printf("% x\n", sample)

    fmt.Println("Printf with %q:")
    fmt.Printf("%q\n", sample)

    fmt.Println("Printf with %+q:")
    fmt.Printf("%+q\n", sample)
}

Выполнить в песочнице

UTF-8 и строковые литералы

Как мы видели, индексирование строки дает ее байты, а не символы: строка - это просто набор байтов. Это означает, что когда мы храним символьное значение в строке, мы храним его представление в байтах-за-раз. Давайте посмотрим на более контролируемый пример, чтобы увидеть, как это происходит.

Вот простая программа, которая печатает строковую константу с одним символом тремя различными способами, один раз как обычную строку, один раз как только-ASCII строку в кавычках и один раз как отдельные байты в шестнадцатеричном формате. Чтобы избежать путаницы, мы создаем "сырую строку" (raw string), заключенную в обратные кавычки, чтобы она могла содержать только буквальный текст. (Обычные строки, заключенные в двойные кавычки, могут содержать escape-последовательности, как мы показали выше.)

func main() {
    const placeOfInterest = `⌘`

    fmt.Printf("plain string: ")
    fmt.Printf("%s", placeOfInterest)
    fmt.Printf("\n")

    fmt.Printf("quoted string: ")
    fmt.Printf("%+q", placeOfInterest)
    fmt.Printf("\n")

    fmt.Printf("hex bytes: ")
    for i := 0; i < len(placeOfInterest); i++ {
        fmt.Printf("%x ", placeOfInterest[i])
    }
    fmt.Printf("\n")
}

Запустить в песочнице

Вывод:

plain string: ⌘
quoted string: "\u2318"
hex bytes: e2 8c 98

что напоминает нам, что значение Unicode символа U+2318, символ "Достопримечательность" ⌘, представлено байтами e2 8c 98, и что эти байты являются кодировкой UTF-8 шестнадцатеричного значения 2318.

Это может быть очевидным или нет, в зависимости от вашего знакомства с UTF-8, но стоит потратить некоторое время на объяснение того, как было создано представление строки в UTF-8. Простой факт: оно было создано, когда исходный код был написан.

Исходный код на Go определен как текст UTF-8; никакое другое представление не допускается. Это означает, что когда в исходном коде мы пишем текст

`⌘`

текстовый редактор, использованный для создания программы, помещает кодировку UTF-8 символа ⌘ в исходный текст. Когда мы распечатываем шестнадцатеричные байты, мы просто выгружаем данные, которые редактор поместил в файл.

Короче говоря, исходный код Go - это UTF-8, поэтому исходный код строкового литерала - это текст UTF-8. Если этот строковый литерал не содержит escape-последовательностей, чего не может необработанная строка, созданная строка будет содержать именно исходный текст между кавычками. Таким образом, по определению и по конструкции необработанная строка всегда будет содержать действительное представление UTF-8 своего содержимого. Точно так же, если только он не содержит выводов, нарушающих UTF-8, как в предыдущем разделе, обычный строковый литерал также всегда будет содержать действительный UTF-8.

Некоторые люди думают, что строки Go - это всегда UTF-8, но это не так: только строковые литералы являются UTF-8. Как мы показали в предыдущем разделе, строковые значения могут содержать произвольные байты; как мы показали в этом, строковые литералы всегда содержат текст UTF-8, если у них нет экранирования на уровне байтов.

Подводя итог, можно сказать, что строки могут содержать произвольные байты, но при построении из строковых литералов эти байты (почти всегда) являются UTF-8.

Кодовые точки, символы и руны

До сих пор мы были очень осторожны в том, как мы используем слова "байт" и "символ". Это отчасти потому, что строки содержат байты, а отчасти потому, что понятие "символ" немного сложно определить. Стандарт Unicode использует термин "кодовая точка" ("code point") для обозначения элемента, представленного одним значением. Кодовая точка U+2318 с шестнадцатеричным значением 2318 представляет символ ⌘.

Чтобы выбрать более прозаичный пример, кодовая точка Unicode U+0061 - это строчная латинская буква 'A': a.

Но как насчет строчной буквы с акцентом 'A', à? Это символ, и это также кодовая точка (U+00E0), но у него есть другие представления. Например, мы можем использовать "комбинирующую" кодовую точку с акцентом U+0300 и прикрепить ее к строчной букве a U+0061, чтобы создать тот же символ à. В общем, символ может быть представлен несколькими различными последовательностями кодовых точек и, следовательно, различными последовательностями UTF-8 байтов.

Следовательно, концепция символа в вычислениях неоднозначна или, по крайней мере, сбивает с толку, поэтому мы используем ее с осторожностью. Чтобы сделать вещи надежными, существуют методы нормализации, которые гарантируют, что данный символ всегда представлен одними и теми же кодовыми точками, но этот предмет пока уводит нас слишком далеко от темы.

"Кодовая точка" - это нечто вроде труднопроизносимого слова, поэтому Go вводит более короткое понятие: руна (rune). Термин появляется в библиотеках и исходном коде и означает то же самое, что и "кодовая точка", с одним интересным дополнением.

Язык Go определяет слово rune как псевдоним для типа int32, поэтому программы могут быть очищены, когда целочисленное значение представляет кодовую точку. Более того, то, что вы можете рассматривать как символьную константу, называется рунной константой (rune constant) в Go. Тип и значение выражения

'⌘'

это руна (rune) с целочисленным значением 0x2318.

Подводя итог, вот основные моменты:

  • Исходный код в Go всегда UTF-8.
  • Строка хранит произвольные байты.
  • Строковый литерал, без экранирования на уровне байтов, всегда содержит допустимые последовательности UTF-8.
  • Эти последовательности представляют кодовые точки Unicode, называемые рунами.
  • В Go нет гарантии, что символы в строках нормализованы.

Range циклы

Помимо аксиоматической детализации, что исходным кодом Go является UTF-8, на самом деле есть только один способ, которым Go обрабатывает UTF-8 специально, а именно при использовании цикла for для строки.

Мы видели, что происходит с обычным циклом for. Цикл for range, напротив, декодирует одну руну в кодировке UTF-8 на каждую итерацию. Каждый раз, когда происходит цикл, индекс цикла - это начальная позиция текущей руны, измеряемая в байтах, а кодовая точка - ее значение. Вот пример, использующий еще один удобный формат Printf, %#U, который показывает значение Unicode кодовой точки и ее печатное представление:

const nihongo = "日本語"
for index, runeValue := range nihongo {
    fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}

Запустить в песочнице

Выходные данные показывают, как каждая кодовая точка занимает несколько байтов:

U+65E5 '日' starts at byte position 0
U+672C '本' starts at byte position 3
U+8A9E '語' starts at byte position 6

Библиотеки

Стандартная библиотека Go предоставляет мощную поддержку для интерпретации текста UTF-8. Если цикл for range не подходит для ваших целей, скорее всего, средство, которое вам нужно, предоставляется пакетом из библиотеки.

Наиболее важным таким пакетом является unicode/utf8, который содержит вспомогательные процедуры для проверки, дизассемблирования и повторной сборки строк UTF-8. Вот программа, эквивалентная приведенному выше примеру for range, но для выполнения работы используется функция DecodeRuneInString из этого пакета. Возвращаемыми значениями функции являются руна и ее ширина в байтах в кодировке UTF-8.

const nihongo = "日本語"
for i, w := 0, 0; i < len(nihongo); i += w {
    runeValue, width := utf8.DecodeRuneInString(nihongo[i:])
    fmt.Printf("%#U starts at byte position %d\n", runeValue, i)
    w = width
}

Запустить в песочнице

Запустите его, чтобы увидеть, что он выполняет то же самое. Цикл for range и DecodeRuneInString определены для создания точно такой же итерационной последовательности.

Посмотрите документацию по пакету unicode/utf8, чтобы увидеть, какие другие возможности он предоставляет.

Заключение

Строки строятся из байтов, поэтому их индексация дает байты, а не символы. Строка может даже не содержать символов. На самом деле, определение "символ" является неоднозначным, и было бы ошибкой пытаться разрешить эту неоднозначность, определяя, что строки состоят из символов.

Строки Go могут содержать произвольные байты, UTF-8 является центральной частью их структуры.


Читайте также:


четверг, 13 июня 2019 г.

Спецификация Go: преобразования в и из строкового типа

1. Преобразование целочисленного значения со знаком или без знака в строковый тип дает строку, содержащую представление целого числа в UTF-8. Значения за пределами диапазона допустимых кодовых точек Unicode преобразуются в "\uFFFD".

string('a')       // "a"
string(-1)        // "\ufffd" == "\xef\xbf\xbd"
string(0xf8)      // "\u00f8" == "ø" == "\xc3\xb8"
type MyString string
MyString(0x65e5)  // "\u65e5" == "日" == "\xe6\x97\xa5"

2. Преобразование среза байтов в строковый тип дает строку, последовательные байты которой являются элементами среза.

string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"
string([]byte{})                                     // ""
string([]byte(nil))                                  // ""

type MyBytes []byte
string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})  // "hellø"

3. Преобразование среза рун в строковый тип дает строку, которая является объединением отдельных значений рун, преобразованных в строки.

string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{})                         // ""
string([]rune(nil))                      // ""

type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"

4. Преобразование значения типа строки в тип среза байтов дает срез, последовательными элементами которого являются байты строки.

[]byte("hellø")   // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]byte("")        // []byte{}

MyBytes("hellø")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}

5. Преобразование значения типа строки в срез типа руны дает срез, содержащий отдельные кодовые точки Unicode строки.

[]rune(MyString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("")                 // []rune{}

MyRunes("白鵬翔")           // []rune{0x767d, 0x9d6c, 0x7fd4}


Читайте также: