现在位置: 首页 > Swift 教程 > 正文

Swift 数据类型

Swift 是强类型语言,每个值都有确定的类型,类型决定了它能存什么、能做什么运算。

本章节按「数值 → 文本 → 元组 → 集合 → 特殊类型」的顺序,把 Swift 的基础数据类型过一遍。

其中数组、字典、集合只做概览,详细用法在后面的专门章节展开。

Swift 类型体系总览:值类型与引用类型的分叉,值类型含基础类型、集合类型与自定义类型,引用类型含 class 与闭包,并标注 Any 可容纳两者、AnyObject 只容纳引用类型


https://www.runoob.com/wp-content/uploads/2015/10/05-type-system.svg

整数类型

Swift 的整数分为有符号和无符号两大类,各自又有不同的位宽。

最常用的是 Int,它的位宽跟随平台,在 64 位设备上等同于 Int64。

实例

// 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 两个静态属性,直接读就能拿到边界值,不要手写这些数字。

类型位宽取值范围
Int88 位-128 ~ 127
UInt88 位0 ~ 255
Int1616 位-32768 ~ 32767
Int3232 位-2147483648 ~ 2147483647
Int6464 位-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 isOpen = true          // Bool:只有 true 和 false
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。

实例

// 字符串字面量可以转成 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 篇讲写时复制时展开。

实例

import Foundation

// 数组:有序,元素类型一致
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 空值安全的核心,几乎所有可能失败的操作都会返回可选类型。

实例

// 可选类型:值可能存在,也可能为 nil
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 可以表示任何类型的值,包括值类型、引用类型、函数类型。

实例

// 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 表示任意类类型的实例,它不能表示结构体、枚举这类值类型。

实例

// AnyObject 表示任意类类型的实例
class Site {}
let obj: AnyObject = Site()
print(type(of: obj))

运行结果:

Site

Never 表示「永远不会返回」的类型,通常用在必然终止程序或必然抛错的函数上。

实例

// 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。