Есть ли простой способ указать символьные литералы в Swift?
Swift, похоже, пытается обесценить понятие строки, состоящей из массива атомных символов, что имеет смысл для многих применений, но там очень много программирования, которое включает в себя сбор данных по объектам, которые являются ASCII для всех практических целей: особенно с файловыми вводами/выводами. Отсутствие встроенной функции языка для указания символьного литерала кажется щелевой дырой, т.е. Нет аналога C/Java/etc-esque:
String foo="a"
char bar='a'
Это довольно неудобно, потому что даже если вы преобразуете свои строки в массивы символов, вы не можете делать такие вещи, как:
let ch:unichar = arrayOfCharacters[n]
if ch >= 'a' && ch <= 'z' {...whatever...}
Один довольно хакерский способ обхода - сделать что-то вроде этого:
let LOWCASE_A = ("a" as NSString).characterAtIndex(0)
let LOWCASE_Z = ("z" as NSString).characterAtIndex(0)
if ch >= LOWCASE_A && ch <= LOWCASE_Z {...whatever...}
Это работает, но, очевидно, это довольно уродливо. Кто-нибудь имеет лучший способ?
Ответы
Ответ 1
Character
может быть создан из String
, если те String
состоят только из одного символа. И, поскольку Character
реализует ExtendedGraphemeClusterLiteralConvertible
, Swift сделает это для вас автоматически при назначении. Итак, чтобы создать Character
в Swift, вы можете просто сделать что-то вроде:
let ch: Character = "a"
Затем вы можете использовать метод contains
для IntervalType
(сгенерированный с помощью Range
операторов), чтобы проверить, символ находится в пределах диапазона, который вы ищете:
if ("a"..."z").contains(ch) {
/* ... whatever ... */
}
Пример:
let ch: Character = "m"
if ("a"..."z").contains(ch) {
println("yep")
} else {
println("nope")
}
Выходы:
да
Обновление: Как указывал @MartinR, упорядочение символов Swift основано на Unicode Normalization Form D который не в том же порядке, что и коды символов ASCII. В вашем конкретном случае между a
и z
больше символов, чем в прямом ASCII (например, ä
). Подробнее см. @MartinR здесь.
Если вам нужно проверить, находится ли символ между двумя символьными кодами ASCII, вам может понадобиться сделать что-то вроде вашего обходного пути. Однако вам также придется преобразовать ch
в unichar
, а не в Character
, чтобы он работал (см. этот вопрос для получения дополнительной информации о Character
vs unichar
):
let a_code = ("a" as NSString).characterAtIndex(0)
let z_code = ("z" as NSString).characterAtIndex(0)
let ch_code = (String(ch) as NSString).characterAtIndex(0)
if (a_code...z_code).contains(ch_code) {
println("yep")
} else {
println("nope")
}
Или, еще более подробный способ без использования NSString
:
let startCharScalars = "a".unicodeScalars
let startCode = startCharScalars[startCharScalars.startIndex]
let endCharScalars = "z".unicodeScalars
let endCode = endCharScalars[endCharScalars.startIndex]
let chScalars = String(ch).unicodeScalars
let chCode = chScalars[chScalars.startIndex]
if (startCode...endCode).contains(chCode) {
println("yep")
} else {
println("nope")
}
Примечание. Оба этих примера работают только в том случае, если символ содержит только одну кодовую точку, но при условии, что мы ограничены ASCII, это не должно быть проблемой. p >
Ответ 2
Если вам нужны литералы ASCII C-стиля, вы можете просто сделать это:
let chr = UInt8(ascii:"A") // == UInt8( 0x41 )
Или, если вам нужны 32-разрядные литералы в Юникоде, вы можете сделать это:
let unichr1 = UnicodeScalar("A").value // == UInt32( 0x41 )
let unichr2 = UnicodeScalar("é").value // == UInt32( 0xe9 )
let unichr3 = UnicodeScalar("😀").value // == UInt32( 0x1f600 )
Или 16-бит:
let unichr1 = UInt16(UnicodeScalar("A").value) // == UInt16( 0x41 )
let unichr2 = UInt16(UnicodeScalar("é").value) // == UInt16( 0xe9 )
Все эти инициализаторы будут оцениваться во время компиляции, поэтому на самом деле он использует немедленный литерал на уровне инструкции сборки.
Ответ 3
Предложенную вами функцию предлагалось включить в Swift 5.1, но это предложение было отклонено по нескольким причинам:
-
неоднозначность
Предложение, как написано, в текущей экосистеме Swift, позволило бы использовать выражения типа 'x' + 'y' == "xy"
, которые не были предназначены (правильный синтаксис был бы "x" + "y" == "xy"
).
-
укрупнение
Предложение было два в одном.
Во-первых, он предложил способ введения литералов в одну кавычку в язык.
Во-вторых, он предложил преобразовать их в числовые типы для работы со значениями ASCII и кодовыми точками Unicode.
Это оба хорошие предложения, и было рекомендовано разделить их на две части и повторно предложить. Эти последующие предложения еще не оформлены.
-
разногласие
Он так и не достиг консенсуса относительно того, будет ли тип по умолчанию 'x'
Character
или Unicode.Scalar
. Предложение пошло с Character
, цитируя Принцип Наименьшего Сюрприза, несмотря на это отсутствие консенсуса.
Вы можете прочитать полное обоснование отказа здесь.
Синтаксис может выглядеть следующим образом:
let myChar = 'f' // Type is Character, value is solely the unicode U+0066 LATIN SMALL LETTER F
let myInt8: Int8 = 'f' // Type is Int8, value is 102 (0x66)
let myUInt8Array: [UInt8] = [ 'a', 'b', '1', '2' ] // Type is [UInt8], value is [ 97, 98, 49, 50 ] ([ 0x61, 0x62, 0x31, 0x32 ])
switch someUInt8 {
case 'a' ... 'f': return "Lowercase hex letter"
case 'A' ... 'F': return "Uppercase hex letter"
case '0' ... '9': return "Hex digit"
default: return "Non-hex character"
}
Ответ 4
Также похоже, что вы можете использовать следующий синтаксис:
Character("a")
Это создаст Character
из указанной строки одного символа.
Я проверял это только в Swift 4 и Xcode 10.1