Swift 数据类型
Swift 是强类型语言,每个值都有确定的类型,类型决定了它能存什么、能做什么运算。
本章节按「数值 → 文本 → 元组 → 集合 → 特殊类型」的顺序,把 Swift 的基础数据类型过一遍。
其中数组、字典、集合只做概览,详细用法在后面的专门章节展开。
https://www.runoob.com/wp-content/uploads/2015/10/05-type-system.svg
整数类型
Swift 的整数分为有符号和无符号两大类,各自又有不同的位宽。
最常用的是 Int,它的位宽跟随平台,在 64 位设备上等同于 Int64。
实例
print("Int 最大值:\(Int.max)")
print("Int 最小值:\(Int.min)")
// 固定宽度的整数类型
print("Int8 范围:\(Int8.min) ~ \(Int8.max)")
print("UInt8 范围:\(UInt8.min) ~ \(UInt8.max)")
print("Int64 最大值:\(Int64.max)")
运行结果:
Int 最大值:9223372036854775807 Int 最小值:-9223372036854775808 Int8 范围:-128 ~ 127 UInt8 范围:0 ~ 255 Int64 最大值:9223372036854775807
每个整数类型都有 .min 和 .max 两个静态属性,直接读就能拿到边界值,不要手写这些数字。
| 类型 | 位宽 | 取值范围 |
|---|---|---|
| Int8 | 8 位 | -128 ~ 127 |
| UInt8 | 8 位 | 0 ~ 255 |
| Int16 | 16 位 | -32768 ~ 32767 |
| Int32 | 32 位 | -2147483648 ~ 2147483647 |
| Int64 | 64 位 | -9223372036854775808 ~ 9223372036854775807 |
| UInt | 跟随平台 | 0 ~ 平台最大无符号整数 |
除非确实需要指定位宽,否则一律用 Int,这样代码在 32 位和 64 位平台上都能正确运行。
整数运算溢出时默认会直接终止程序,而不是像 C 语言那样静默回绕,这能避免很多隐蔽的 bug。
注意:如果确实需要溢出回绕,要用溢出运算符 &+、&-、&*,普通运算符 + 溢出会触发陷阱。
浮点类型
Swift 提供两种浮点类型:Double 表示双精度,Float 表示单精度。
没有类型标注时,带小数点的字面量一律推断为 Double。
实例
print("Double 最大值:\(Double.greatestFiniteMagnitude)")
print("Double 最小正数:\(Double.leastNonzeroMagnitude)")
print("Float 最大值:\(Float.greatestFiniteMagnitude)")
print("Double 有效位数:\(Double.significandBitCount) 位二进制")
运行结果:
Double 最大值:1.7976931348623157e+308 Double 最小正数:5e-324 Float 最大值:3.4028235e+38 Double 有效位数:52 位二进制
| 类型 | 精度 | 典型场景 |
|---|---|---|
| Double | 约 15 位十进制有效数字 | 默认选择,绝大多数计算 |
| Float | 约 6 位十进制有效数字 | 内存敏感、精度要求不高的图形数据 |
浮点数不能直接用 == 比较,因为二进制无法精确表示 0.1 这类十进制小数,应该比较差值的绝对值是否小于某个容差。
注意:浮点类型的边界要用 .greatestFiniteMagnitude、.leastNonzeroMagnitude 这类内置属性获取,不要手写 1.7976931348623157e+308 这样的数字,容易写错且可读性差。
布尔类型
Swift 的布尔类型只有 Bool,取值只有 true 和 false 两个。
这一点和其他语言不同:nil 不是布尔值,整数 0 也不能当布尔用。
实例
let grade: Character = "A" // Character:单个字符
let site: String = "RUNOOB"
print(isOpen)
print(grade)
print(site)
print(type(of: isOpen))
print(type(of: grade), type(of: site))
运行结果:
true A RUNOOB Bool Character String
所有条件表达式都必须是 Bool 类型,if count 或 if optionalValue 都是不合法的写法。
布尔字面量只有两个值,没有第三个,判断是否为空要用 isEmpty,判断可选值是否存在要用可选绑定。
字符与字符串
Character 表示一个字符,String 表示一串字符,两者可以互相转换。
Swift 的字符以「扩展字素簇」为单位,所以一个 emoji 或者一个带音标的字母也算一个 Character。
实例
let letter = Character("S")
print(letter, type(of: letter))
// String 可以直接拼接 Character
var site = "www.runoob"
site.append(".")
site.append(contentsOf: "com")
print(site)
运行结果:
S Character www.runoob.com
用 Character("S") 转换时,如果字符串里有多个字符,程序会在运行时终止,所以这个构造器只适合确定长度为 1 的场景。
字符串的索引不是整数,而是 String.Index 类型,因为不同字符占用的字节数可能不同,这一点会在第 8 篇详细讲。
元组
元组把多个不同类型的值组合成一个整体,适合临时返回多个结果的场景。
元组的元素可以没有名字,用下标访问;也可以命名,用名字访问。
实例
let site = ("RUNOOB", "www.runoob.com")
// 按下标访问
print(site.0, site.1)
// 命名元素
let course = (name: "Swift 教程", chapters: 43)
print(course.name)
print(course.chapters)
// 解构成单独的常量
let (name, url) = site
print(name, url)
运行结果:
RUNOOB www.runoob.com Swift 教程 43 RUNOOB www.runoob.com
元组适合函数返回多个值这种临时场景,如果这个组合需要反复使用或者需要附加方法,就应该定义成结构体。
元组不能遵循协议,也不能添加方法,这是它和结构体最大的区别。
集合类型概览
Swift 标准库提供三种集合类型:数组、字典、集合。
它们都是值类型,赋值或传参时会复制,具体原理在第 32 篇讲写时复制时展开。
实例
// 数组:有序,元素类型一致
var langs = ["Swift", "Objective-C", "C"]
// 字典:键值对,无序
var sites = ["name": "RUNOOB", "url": "www.runoob.com"]
// 集合:无序且元素唯一
var letters: Set<Character> = ["a", "b", "c", "a"]
print(langs.count, langs[0])
print(sites["name"] ?? "未知")
print(letters.count)
print(letters.sorted())
运行结果:
3 Swift RUNOOB 3 ["a", "b", "c"]
| 类型 | 是否有序 | 元素是否唯一 | 访问方式 |
|---|---|---|---|
| Array | 有序 | 可以重复 | 整数下标 |
| Dictionary | 无序 | 键唯一,值可重复 | 键 |
| Set | 无序 | 唯一 | 成员判断 |
注意上面创建集合时写了两个 "a",结果只保留一个,这就是集合的自动去重。
字典的遍历顺序是不保证的,需要稳定顺序时要先排序,这一点在第 13 篇会专门提醒。
可选类型概览
可选类型 Optional 表示「可能有值,也可能没有值」,用问号 ? 标注。
它是 Swift 空值安全的核心,几乎所有可能失败的操作都会返回可选类型。
实例
var nickname: String? = nil
print(nickname ?? "没有昵称")
nickname = "Runoob"
if let name = nickname {
print("昵称是 \(name)")
}
运行结果:
没有昵称 昵称是 Runoob
?? 是空合运算符,左边为 nil 时取右边的默认值。
可选类型的完整用法,包括强制解包、可选绑定、可选链,会在第 29 篇和第 30 篇详细展开。
注意:可选类型不能直接参与运算,也不能当布尔值用,必须先解包。这是 Swift 与 Objective-C 的一个重要区别。
Any、AnyObject 与 Never
除了具体类型,Swift 还有几个表示「任意类型」的特殊类型。
Any 可以表示任何类型的值,包括值类型、引用类型、函数类型。
实例
var items: [Any] = ["RUNOOB", 43, true, 9.9]
for item in items {
print("\(item) 的类型是 \(type(of: item))")
}
运行结果:
RUNOOB 的类型是 String 43 的类型是 Int true 的类型是 Bool 9.9 的类型是 Double
用 Any 会丢失类型信息,取值时必须用 as? 转换回具体类型,所以除非确实需要,否则不要用。
AnyObject 表示任意类类型的实例,它不能表示结构体、枚举这类值类型。
实例
class Site {}
let obj: AnyObject = Site()
print(type(of: obj))
运行结果:
Site
Never 表示「永远不会返回」的类型,通常用在必然终止程序或必然抛错的函数上。
实例
// 调用 fail 之后程序立即终止,所以下面这段只作定义演示,不演示运行结果
func fail(_ message: String) -> Never {
print("致命错误:\(message)")
fatalError(message) // 程序在此终止
}
因为这类函数会终止程序,所以没有可展示的返回值,实际项目中常用于处理不可能恢复的错误。
| 类型 | 含义 | 典型用途 |
|---|---|---|
| Any | 任意类型的值 | 异构数组、动态派发 |
| AnyObject | 任意类类型的实例 | Objective-C 互操作 |
| Never | 永远不会返回 | fatalError、进程终止函数 |
类型别名 typealias
typealias 给已有类型起一个更好读的名字,不创建新类型。
它常用于让签名更清晰,或者给复杂的泛型类型起短名。
实例
typealias Distance = Double
typealias Site = (name: String, url: String)
let d: Distance = 9.9
let site: Site = ("RUNOOB", "www.runoob.com")
print(d)
print(site.name, site.url)
运行结果:
9.9 RUNOOB www.runoob.com
别名和原类型完全等价,Distance 就是 Double,可以直接混用,不存在隐式转换的问题。
如果需要一个「不能和原类型混用」的新类型,应该用结构体或枚举包装,而不是 typealias。
类型安全与类型推断
Swift 是类型安全的语言,不同类型之间不会隐式转换。
整数和浮点数相加必须显式转换,编译器不会替你做决定,这避免了很多精度丢失的隐患。
实例
let a: Int = 3
let b: Double = 1.5
// 必须显式转换:Double(a)
print(Double(a) + b)
// 类型推断
let inferredInt = 10 // Int
let inferredDouble = 10.0 // Double
let inferredString = "Runoob" // String
print(type(of: inferredInt), type(of: inferredDouble), type(of: inferredString))
运行结果:
4.5 Int Double String
类型推断让代码更简洁,类型安全让错误更早暴露,两者配合是 Swift 用起来「既省事又放心」的关键。
需要查看某个值的类型时用 type(of:),不要用已经废弃的 dynamicType。
常见问题
数据类型阶段常见的疑问集中在数值转换和类型选择上。
Int 和 Double 能直接相加吗
不能,必须写成 Double(a) + b 或者 a + Int(b),显式转换会提醒你注意精度损失。
什么时候用 Float 而不是 Double
只在内存或带宽紧张、精度要求不高时用 Float,比如图形顶点数据;日常计算一律用 Double。
元组和结构体怎么选
临时组合几个值用元组,需要方法、协议或者反复使用就定义结构体。
为什么集合里的重复元素消失了
Set 的定义就是元素唯一,重复插入会被自动忽略,需要保留重复元素请用数组。
Any 和泛型有什么区别
Any 在运行时丢失类型,取值要转换;泛型在编译期保留类型,既灵活又安全,能用泛型就不要用 Any。
