在Swift中是否有一种清晰的方法来指定字符字面量?

26

Swift似乎试图废弃字符串由原子字符数组组成的概念,这对许多用途都是有意义的,但是有很多编程涉及到处理在实际上都是ASCII的数据结构,尤其是文件I/O。缺少一种内置语言功能来指定字符字面量似乎是一个巨大的缺陷,也就是说没有类似于C/Java等的模拟:

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...}

这个方法可以用,但显然很丑陋。有没有更好的方法?


如果你真的想把一个文件当作ASCII处理,那么你可以使用Data类而不是String类。 - Abhi Beckert
5个回答

15

String中只包含一个字符时,可以通过它创建Character。由于Character实现了ExtendedGraphemeClusterLiteralConvertible协议,Swift会在赋值时自动进行转换。因此,要在Swift中创建一个Character,你可以直接执行类似以下代码的操作:

let ch: Character = "a"

然后,您可以使用IntervalTypecontains方法(由Range运算符生成)来检查字符是否在您要查找的范围内:

if ("a"..."z").contains(ch) {
    /* ... whatever ... */
}

例子:

let ch: Character = "m"
if ("a"..."z").contains(ch) {
    println("yep")
} else {
    println("nope")
}

输出:

yep


更新:正如@MartinR所指出的那样,Swift字符排序基于Unicode规范形式D,这与ASCII字符代码的顺序不同。在您特定的情况下,在az之间有更多的字符,而不是直接按ASCII码排序(例如ä)。有关更多信息,请参见@MartinR的答案 这里

如果您需要检查一个字符是否在两个ASCII字符代码之间,则可能需要做类似于原始解决方法的事情。但是,您还必须将ch转换为unichar而不是Character才能使其工作(请参见此问题,了解有关Characterunichar的更多信息):

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,这不应该成为问题。


@MartinR 谢谢,我之前没有意识到这一点。我已经更新了答案并加入了这个信息。 - Mike S
这是一个不错的技巧,我会把它存起来用于其他类型的逻辑。但对于逐个字符解析文件格式且性能和精度同等重要的情况来说,仍然有点高级,因为能够在类似int和string的表示之间来回转换字符仍然是不可替代的。因此,char-literal 仍然是必需的。 - Alex Clark

11

如果您需要 C 风格的 ASCII 字符串,可以这样做:

let chr = UInt8(ascii:"A") // == UInt8( 0x41 )

或者如果你需要32位Unicode文字,你可以这样做:

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 )
所有这些初始化器都将在编译时评估,因此实际上是在汇编指令级别使用即时文字。

4
您想要的功能曾提议在Swift 5.1中实现, 但由于几个原因,该提议被拒绝了:
  1. 歧义

    按照当前的Swift生态系统,该提案允许出现像'x' + 'y' == "xy"这样的表达式,这并不是预期的结果(正确的语法应该是"x" + "y" == "xy")。

  2. 合并

    该提案包含两个方面。

    首先,它提出了一种引入单引号字面量到语言中的方法。

    其次,它建议这些字面量可以转换为数字类型以处理ASCII值和Unicode码点。

    这两个提案都很好,建议将其拆分成两个提案重新提交。这些后续提案尚未正式制定。

  3. 分歧

    关于'x'的默认类型是否为CharacterUnicode.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"
}

2
对此我有些复杂的感受...... 看起来它能够完成任务,但是等待5个大版本来获取上世纪50年代Fortran已经拥有的功能实在是令人沮丧。 - Alex Clark
1
@AlexClark 我个人很高兴他们如此认真地对待这些重大、不可逆转的变化,而不是在一开始就做出错误决定(参见:JavaScript)。而且,“5个主要版本”相当于“大约5年”的时间,与其他语言的发展速度相比,这种速度令人愉悦,因为你可能在十年内甚至永远都看不到一个主要版本。 - Ky -

3

看起来你可以使用以下语法:

Character("a")

这将从指定的单个字符字符串创建一个 Character
我只在 Swift 4 和 Xcode 10.1 中测试过这个功能。

1

我为什么要挖掘七年前的帖子?可能是因为好玩吧?但说真的,我认为我可以为讨论做出贡献。

这不是一个巨大的漏洞,或者说,这是一个有意识的巨大漏洞,明确阻止将一串文本与一系列ASCII字节混淆。

您绝对可以拆分字符串。字符串实现了BidirectionalCollection,并且有许多方法来操作其原子。请参见:https://developer.apple.com/documentation/swift/string。 但您必须习惯更普遍的字符串概念。它可以从用户的角度拆分,这是一系列字形簇,每个字形簇(通常)具有可视分离的外观,或者从编码的角度拆分,其中可以是几种(UTF32、UTF16、UTF8)之一。

冒着过分分析您问题的措辞的风险:

  • 数据结构是概念性的,与存储编码无关。
  • 将数据结构编码为ASCII字符串只是一种ASCII字符串。
  • 按设计,ASCII值0-127的编码在UTF-8中具有相同的编码,因此使用UTF8 API加载该流没有问题。
  • 将数据结构编码为字符串,其中结构的字段具有UTF-8 Unicode字符串值,不是ASCII字符串,而是UTF-8字符串本身。
  • 字符串要么是ASCII编码的,要么不是;“实际目的”不是有意义的限定词。其中99.99%文本落在ASCII范围内(其中编码将匹配)的UTF-8数据库字段,但偶尔不会落在此范围内,将产生一些严重的错误机会。

Swift不采用固定宽度整数和仅英文文本的简洁低级等价方式,而是拥有更丰富的API,强制更明确地命名涉及的类别和实体。如果您想处理ASCII,则有一个名称(方法)可供使用,如果您想处理人类子类别,则也有一个名称可供使用,它们完全独立于彼此。这是事实,而不是传教,它可能会呈现出令人恼火的学习曲线。

(这是针对新手的,承认原帖作者可能已经有多年的经验。)
对于你想要做的事情,考虑以下内容:
let foo = "abcDeé@¶œŎO!@#"

foo.forEach { c in
    print((c.isASCII ? "\(c) is ascii with value \(c.asciiValue ?? 0); " : "\(c) is not ascii; ")
        + ((c.isLetter ? "\(c) is a letter" : "\(c) is not a letter")))
}

b is ascii with value 98; b is a letter
c is ascii with value 99; c is a letter
D is ascii with value 68; D is a letter
e is ascii with value 101; e is a letter
é is not ascii; é is a letter
@ is ascii with value 64; @ is not a letter
¶ is not ascii; ¶ is not a letter
œ is not ascii; œ is a letter
Ŏ is not ascii; Ŏ is a letter
O is ascii with value 79; O is a letter
! is ascii with value 33; ! is not a letter
@ is ascii with value 64; @ is not a letter
# is ascii with value 35; # is not a letter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接