Обращение по индексу к строке

Рейтинг: 0Ответов: 3Опубликовано: 16.02.2023

Почему я не могу вывести определённый символ строки обращаясь к индексам строки через цикл ?

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

Ответы

▲ 0Принят

В вашем коде, вы обращаетесь к индексу строки, чтобы получить байтовое представление символа. Для получения символа, вы можете использовать метод 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() используется для конвертации символа в строку для вывода.

▲ 2

В оперативной памяти строковые литералы хранятся как массивы байтов в кодировке 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

▲ 0

Вы не можете вывести определённый символ строки, обращаясь к индексам строки через цикл, потому что строки в 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]) 
    } 
}