Обращение по индексу к строке
Почему я не могу вывести определённый символ строки обращаясь к индексам строки через цикл ?
package main
import "fmt"
func main() {
var s string = "hello"
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
}
Вывод:
104
101
108
108
111
Почему не:
h
e
l
l
o
Ответы (3 шт):
Вы не можете вывести определённый символ строки, обращаясь к индексам строки через цикл, потому что строки в Go являются неизменяемыми. Поэтому вам нужно преобразовать строку в массив символов, чтобы изменить один или несколько символов.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
var s string = "hello"
runes := []rune(s)
for i := 0; i < utf8.RuneCountInString(s); i++ {
fmt.Println(runes[i])
}
}
В вашем коде, вы обращаетесь к индексу строки, чтобы получить байтовое представление символа. Для получения символа, вы можете использовать метод string():
package main
import "fmt"
func main() {
var s string = "hello"
for i := 0; i < len(s); i++ {
fmt.Println(string(s[i]))
}
}
Или можно обратиться к символу напрямую, используя синтаксис массива:
package main
import "fmt"
func main() {
var s string = "hello"
for _, c := range s {
fmt.Println(string(c))
}
}
В этом случае, range вернет каждый символ строки, а не байты. Функция string() используется для конвертации символа в строку для вывода.
В оперативной памяти строковые литералы хранятся как массивы байтов в кодировке utf-8. Нотация s[i] возвращает i-й байт в памяти. Этот байт совпадает с символом только в случае однобайтовой кодировки, то есть базовой латинницы. Достаточно поменять в вашем примере строку "hello" на строку "Привет", как ваш цикл s[i] начнёт выводить всякие странные значения.
Выбор utf-8 разработчиками Go обусловлен желанием сэкономить память на строковых литералах. Мол, весь мир пишет на английском, поэтому ни к чему хранить кучу нулей в юникодной кодировке с фиксированным размером (UTF-32)
Обратная сторона этой оптимизации - невозможно получить символ по индексу, так как utf-8 тратит разное число байтов на разные символы. Например, базовая латинница занимает один байт, кириллица - два байта, а китайские иероглифы три байта. Не обработав предварительно строку невозможно предсказать, где в памяти находится i-й символ. Поэтому для получения символов в строке без предварительно обработки Go предоставляет только потоковый интерфейс range, который перебирает символы в строке один за другим.
package main
import (
"fmt"
)
func main() {
var s string = "привет"
for i := 0; i < len(s); i++ {
fmt.Printf("%q\n", s[i])
}
for _, symbol := range s {
fmt.Printf("%q\n", symbol)
}
}
Результат
'Ð'
'¿'
'Ñ'
'\u0080'
'Ð'
'¸'
'Ð'
'²'
'Ð'
'µ'
'Ñ'
'\u0082'
'п'
'р'
'и'
'в'
'е'
'т'
Альтернативой range служит приведение к массиву рун (rune) []rune(s). Этот тип используется в Go для представления юникодных символов в 4-х байтовой кодировке. При выполнении этого приведения рантайм пробежит по строке как range, сложит все найденные юникодные символы в массив 32-х битных целых и вернёт получившийся массив как результат.
Если вам нужно один перебрать символ за символом, пользуйтесь range. Если нужно несколько раз выбирать символы из промежуточных позиций, приводите строку к массиву []rune