Swift:如何将字符串转换为UInt?

10

有关 Swift 2.x 的额外细节,请注意:String 不包含 toInt() 方法。 - Paraneetharan Saravanaperumal
@suraj 请不要将“ios”标签添加到与iOS无直接关系的问题上。Swift也适用于OS X和Linux,这些问题可能也适用。谢谢。 - Eric Aya
@Eric D.,感谢您对标签的澄清。 - Suraj Sonawane
5个回答

14

Swift 2/Xcode 7更新:

从Swift 2开始,所有整数类型都有一个(可失败)的构造函数。

init?(_ text: String, radix: Int = default)

这个方法替换了StringtoInt()方法,因此不再需要特定的自定义代码来完成此任务:

print(UInt("1234")) // Optional(1234)
// This is UInt.max on a 64-bit platform:
print(UInt("18446744073709551615")) // Optional(18446744073709551615)
print(UInt("18446744073709551616")) // nil (overflow)
print(UInt("1234x")) // nil (invalid character)
print(UInt("-12")) // nil (invalid character)

Swift 1.x的旧答案:

这看起来有点复杂,但应该适用于UInt完整范围内的所有数字,并且可以正确检测所有可能的错误(例如溢出或尾随无效字符):

extension String {
    func toUInt() -> UInt? {
        if contains(self, "-") {
            return nil
        }
        return self.withCString { cptr -> UInt? in
            var endPtr : UnsafeMutablePointer<Int8> = nil
            errno = 0
            let result = strtoul(cptr, &endPtr, 10)
            if errno != 0 || endPtr.memory != 0 {
                return nil
            } else {
                return result
            }
        }
    }
}

备注:

  • 用于转换的BSD库函数strtoulendPtr设置为输入字符串中第一个 "无效字符",因此如果所有字符都可以转换,则必须保持endPtr.memory == 0。 在转换错误的情况下,全局变量errno被设置为非零值(例如,对于溢出,ERANGE)。

  • 检查负号是必要的,因为strtoul()接受负数(这些负数被转换为具有相同位模式的无符号数)。

  • 当传递给需要char *参数的函数时,Swift字符串会在“幕后”转换为C字符串,因此人们可能会尝试调用strtoul(self, &endPtr, 0)(这就是我在答案的第一个版本中所做的)。问题在于自动创建的C字符串仅是临时的,并且在strtoul()返回时可能已经无效,因此endPtr不再指向输入字符串中的字符。当我在Playground中测试代码时,就出现了这种情况。使用self.withCString { ... }则不会出现此问题,因为C字符串在闭包的执行期间有效。

一些测试:

println("1234".toUInt()) // Optional(1234)
// This is UInt.max on a 64-bit platform:
println("18446744073709551615".toUInt()) // Optional(18446744073709551615)
println("18446744073709551616".toUInt()) // nil (overflow)
println("1234x".toUInt()) // nil (invalid character)
println("-12".toUInt()) // nil (invalid character)

1
如果给负评的人能留下解释性评论,那将会很有帮助。 - Martin R
如何将字符串“0xff0000”转换为UInt? - TomSawyer
1
@TomSawyer 很简单 :-) 令 str = "0xff0000"; 令 p = UInt(String(str.characters.dropFirst(2)),radix: 16) // 可选的(16711680) - user3441734

4
你可能对类似的更安全的解决方案感兴趣:
let uIntString = "4"
let nonUIntString = "foo"

extension String {
    func toUInt() -> UInt? {
        let scanner = NSScanner(string: self)
        var u: UInt64 = 0
        if scanner.scanUnsignedLongLong(&u)  && scanner.atEnd {
            return UInt(u)
        }
        return nil
    }
}

uIntString.toUInt()     // Optional(4)
nonUIntString.toUInt()  // nil

希望这有所帮助。//经过@Martin R.建议后编辑。

这里的一个问题是它会接受字符串末尾的垃圾字符,这与类似 toInt() 函数的行为不匹配,即 "4blah".toInt() 返回 nil,但 "4blah".toUInt() 返回 4 - Airspeed Velocity
这个问题可以通过添加测试 && scanner.atEnd 轻松解决。另一个问题是没有检测到溢出 - Martin R
@Martin R. 可以通过扫描 Decimal 并进行适当的检查来添加 overflow 检测。这个快速解决方案可以适用于大多数常见情况,避免了 ! 崩溃。 - Matteo Piombo
哎呀,今天我学到了一个新知识,原来 NSScanner 会默默地吞掉溢出的部分,这也太不友好了吧。 - Airspeed Velocity

3
请不要使用来完成此操作,以免导致崩溃。
只需在toInt后面附加map,就可以将其转换为UInt可选项。
let str = "4"
let myUInt = str.toInt().flatMap { $0 < 0 ? nil : UInt($0) }

然后在myUInt上使用常规展开技术

如果您发现自己需要经常这样做:

extension String {
    func toUInt() -> UInt? {
        return self.toInt().flatMap { $0 < 0 ? nil : UInt($0) }
    }
}

let str = "-4"
if let myUInt = str.toUInt() {
    println("yay, \(myUInt)")
}
else {
    println("nuh-uh")
}

编辑:正如@MartinR所指出的那样,虽然安全,但这并不能提取Int无法覆盖的所有可能值的完整范围,请参见其他两个答案。


第一个应该不是flatMap()吗? - oisdk
1
如果数字超出signed整数的范围,那么这个(以及前两个答案)都不起作用。 - Martin R
哦,是的,你完全正确,我把额外的检查提到了第一个例子,但忘记修改映射。 - Airspeed Velocity
@MartinR 的观点非常好,如果是这样的话,那么 @Matteo 的 NSScanner 解决方案就是正确的选择。 - Airspeed Velocity

1
使用强制解包或可选绑定来确保字符串可以转换为UInt。 例如:
let string = "123"
let number = UInt(string) //here number is of type *optional UInt*

//Forced Unwrapping

if number != nil {
  //converted to type UInt
}

-1

只需使用UInt的init方法:

let someString = "4"

UInt(someString.toInt()!) // 4

如果转换失败,这将生成一个__运行时异常__。 - Antonio

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