Почему s[0] не работает
Во многих языках первый символ строки достают по номеру: s[0]. В Swift такая запись не компилируется:
let text = "café"
print(text[0]) // ✗error: 'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.Компилятор говорит прямо: строку нельзя спрашивать по номеру типа Int, нужен индекс типа String.Index. Что это такое и чем плох номер?
Вспомните нитку с бусинами. Крупные бусины на ней разного размера: под одной висит одна мелкая, под другой две, под третьей четыре. Чтобы найти третью крупную бусину, нельзя отмерить линейкой два одинаковых шага. Придётся пройти нитку с начала и отсчитать бусины по одной.
Почему Swift запрещает номер
Если бы все символы занимали одинаковое число байтов, номер символа сразу давал бы его место в памяти. В UTF-8 размеры разные. Чтобы найти символ номер 1000, Swift пришлось бы пройти 999 символов перед ним.
Запись s[1000] выглядела бы мгновенной, а работала бы медленно. В цикле такие медленные шаги складывались бы незаметно. Поэтому Swift не даёт ей скомпилироваться и предлагает способы, в которых этот проход виден в коде.
Этот вопрос часто задают на собеседовании: почему в Swift нельзя написать s[0]? Хороший ответ: символы занимают разное число байтов, поэтому символ по номеру можно найти только проходом от начала строки. Вместо номеров Swift использует индексы String.Index.
Символы с краёв строки
Чаще всего нужен не символ в середине, а начало или конец строки. Для этого у строки есть готовые средства:
let word = "Swift"
let empty = ""
print(word.first ?? "?", word.last ?? "?") // S t
print(empty.first ?? "?") // ?
print(word.prefix(3), word.dropFirst()) // Swi wift
print(word.prefix(10)) // Swiftfirst и last
first — первый символ строки, last — последний. У пустой строки их нет, поэтому оба возвращают опционал Character?: символ или nil. Перед печатью коробку открывают, здесь через ??:
word.first ?? "?"даётS, аword.last ?? "?"—t.- У
emptyпервого символа нет, и??подставляет запасной символ"?".
prefix и dropFirst
prefix(3)даёт первые три символа:Swi.dropFirst()даёт всё, кроме первого символа:wift.prefix(10)при пяти символах отдаёт всю строку и не завершает программу аварийно.
Результат prefix и dropFirst имеет тип Substring — это часть исходной строки. print печатает Substring как обычную строку. Чтобы получить отдельную строку String, оберните результат: String(word.prefix(3)).
Индекс строки
Символ в середине строки достают по индексу строки. Это значение типа String.Index: оно помнит место символа в строке, а не его номер. Индекс получают от самой строки:
let third = word.index(word.startIndex, offsetBy: 2)
print(word[third]) // iindex(_:offsetBy:)
word.startIndex— место первого символа.word.index(word.startIndex, offsetBy: 2)отсчитывает от него два символа вперёд и возвращает индекс третьего.word[third]достаёт символ по этому индексу — буквуi.
Запись длинная, и в этом её смысл: в коде видно, что Swift отсчитывает символы от начала строки.
Здесь картинка с бусинами неточна. На нитке нужную бусину находят глазами, а индекс String.Index можно получить только у самой строки — например, отсчитав символы от startIndex.
Частая ошибка
Последний символ попробовали достать через endIndex:
let city = "Oslo"
print(city[city.endIndex]) // ✗Fatal error: String index is out of boundsПрограмма компилируется, но аварийно завершается. endIndex — это не место последнего символа, а место сразу за ним, как конец нитки после последней бусины. Символа там нет. Последний символ даёт city.last.
Главное
- Строку нельзя индексировать номером:
s[0]не компилируется, потому что символы занимают разное число байтов. firstиlastвозвращаютCharacter?,prefix(n)даёт первыеnсимволов, аdropFirst()— всё, кроме первого.- Символ в середине достают по индексу:
s[s.index(s.startIndex, offsetBy: 2)]. endIndexуказывает за последний символ, поэтомуs[s.endIndex]аварийно завершает программу.