Swift语言基础:从语法到可运行代码的完整认知
Swift 是 Apple 生态中面向 iOS、macOS、watchOS 和 tvOS 应用开发的核心编程语言。它由 Chris Lattner 领导的团队于 2014 年发布,旨在替代 Objective-C,提供更安全、更高效、更易读的编程体验。对于从零开始学习 iOS 开发的学员而言,掌握 Swift 的基本语法不仅是编写 UI 界面的前提,更是理解内存管理、并发编程、协议导向设计等高级主题的基石。本章将系统性地拆解 Swift 的变量与常量、数据类型、运算符、控制流以及函数定义,帮助你建立一套完整的语法认知框架,并规避初学者最常见的编译错误与逻辑陷阱。
上图展示了本章的知识框架。变量与常量是数据存储的容器,数据类型决定了数据的性质与操作方式,运算符用于加工数据,控制流则让代码具备决策与循环能力,而函数将一系列操作封装为可复用的逻辑单元。这五个模块环环相扣,构成了 Swift 程序的最小可运行单元。下面我们将逐一深入剖析。
核心原理:Swift 的类型安全与类型推断机制
Swift 是一门强类型、静态类型的语言。这意味着每一个变量和常量在编译时必须有明确的类型,且类型一旦确定便不能隐式转换。这种设计极大地减少了运行时因类型不匹配而导致的崩溃。然而,Swift 并不要求开发者总是显式标注类型,它通过类型推断(Type Inference)机制,在声明时根据赋值的字面量自动推导出类型。例如:
let age = 25 // 自动推断为 Int 类型
var price = 19.99 // 自动推断为 Double 类型
let name = "Alice" // 自动推断为 String 类型
let isActive = true // 自动推断为 Bool 类型
类型安全还体现在类型转换的严格性上。在 Objective-C 中,你可以将整数赋值给浮点数变量,系统会隐式转换;但在 Swift 中,以下代码会直接报编译错误:
var count: Int = 10
var total: Double = count // 错误:Cannot assign value of type 'Int' to type 'Double'
正确的做法是显式转换:var total: Double = Double(count)。这种强制转换虽然增加了代码量,但避免了因精度丢失或溢出导致的隐性 bug。
变量与常量:var 与 let 的选择策略
在 Swift 中,使用 var 声明变量(值可修改),使用 let 声明常量(值不可修改)。这不仅是语法习惯,更是代码安全性的重要保障。Apple 官方推荐:优先使用 let,只有在值确实需要变化时才使用 var。原因在于:
- 可读性:看到
let就知道该值在整个生命周期内不会改变,减少阅读时的认知负担。 - 并发安全:常量天然是线程安全的,因为不可变对象无需加锁。
- 编译器优化:编译器可以对常量进行更积极的优化,提升运行效率。
来看一个实际场景:
let maxLoginAttempts = 3
var currentAttempts = 0
func login() {
if currentAttempts < maxLoginAttempts {
currentAttempts += 1
print("登录中...")
} else {
print("尝试次数已用尽")
}
}
在这个例子中,maxLoginAttempts 是业务规则,不应被修改,因此用 let;currentAttempts 是状态,需要递增,因此用 var。这种清晰的区分让代码的意图一目了然。
数据类型:从基础类型到集合类型
Swift 的基础数据类型包括:
| 类型 | 说明 | 示例 | 内存占用(64位系统) |
|---|---|---|---|
| Int | 整数,自动适配平台位数 | let n = 42 | 8 字节 |
| Double | 64位浮点数,精度高 | let pi = 3.14159 | 8 字节 |
| Float | 32位浮点数,精度较低 | let f: Float = 3.14 | 4 字节 |
| Bool | 布尔值,true/false | let flag = false | 1 字节 |
| String | 字符串,值类型 | let s = "Hello" | 动态 |
| Character | 单个字符 | let c: Character = "A" | 动态 |
除了基础类型,Swift 还提供了强大的集合类型:Array(有序数组)、Set(无序无重复集合)、Dictionary(键值对字典)。这些集合类型都是值类型,意味着它们在赋值或传递时会进行拷贝,而不是引用共享。这在多线程环境中尤其重要。
var fruits: [String] = ["Apple", "Banana", "Orange"]
var uniqueNumbers: Set<Int> = [1, 2, 3, 3, 2] // 实际为 {1, 2, 3}
var capitals: [String: String] = ["China": "Beijing", "Japan": "Tokyo"]
注意,数组和字典在使用时需要注意可选性。例如,通过下标访问字典时,返回的是一个可选值:
let capitalOfChina = capitals["China"] // 类型为 String?,值为 Optional("Beijing")
let capitalOfFrance = capitals["France"] // 类型为 String?,值为 nil
这种设计迫使开发者处理键不存在的情况,从而避免空指针异常。
运算符:不仅仅是加减乘除
Swift 的运算符分为以下几类:
- 赋值运算符:
=,以及复合赋值+=、-=、*=等。 - 算术运算符:
+、-、*、/、%(取余)。注意,整数除法会截断小数部分。 - 比较运算符:
==、!=、>、<、>=、<=,返回 Bool 值。 - 逻辑运算符:
&&(与)、||(或)、!(非)。 - 范围运算符:
...(闭区间)、..<(半开区间),常用于循环和数组切片。 - 三元运算符:
condition ? value1 : value2,简洁的条件赋值。
一个容易混淆的点是 % 运算符对负数的处理。在 Swift 中,-7 % 3 的结果是 -1,因为结果的符号跟随被除数。这与 Python 不同(Python 中结果为 2)。在金融计算或循环索引时,务必注意这一点。
另外,Swift 支持运算符重载,你可以为自定义类型定义 +、== 等运算符的行为。例如,为坐标结构体实现加法:
struct Point {
var x: Int
var y: Int
static func + (left: Point, right: Point) -> Point {
return Point(x: left.x + right.x, y: left.y + right.y)
}
}
let p1 = Point(x: 1, y: 2)
let p2 = Point(x: 3, y: 4)
let p3 = p1 + p2 // Point(x: 4, y: 6)
控制流:if、switch 与循环的实战运用
控制流是程序逻辑的骨架。Swift 的 if 语句要求条件必须是 Bool 类型,这避免了 C 语言中非零即真的隐式转换。例如:
let score = 85
if score >= 90 {
print("优秀")
} else if score >= 60 {
print("及格")
} else {
print("不及格")
}
switch 语句在 Swift 中远比 C 语言强大。它不需要显式 break,每个分支执行完自动退出;且支持区间匹配、元组匹配、值绑定和 where 条件过滤。例如:
let grade = "B"
switch grade {
case "A":
print("90-100分")
case "B", "C":
print("60-89分")
case "D":
print("不及格")
default:
print("无效成绩")
}
循环方面,Swift 提供 for-in、while 和 repeat-while。其中 for-in 是最常用的,可以遍历区间、数组、字典等。注意,如果你不需要使用循环变量,可以用下划线 _ 代替:
for _ in 1...5 {
print("执行5次")
}
let names = ["Alice", "Bob", "Charlie"]
for name in names {
print("Hello, \(name)")
}
在循环中,break 用于提前终止整个循环,continue 用于跳过当前迭代。对于嵌套循环,可以使用标签(label)来指定要跳出或继续的循环:
outerLoop: for i in 1...3 {
for j in 1...3 {
if i == 2 && j == 2 {
break outerLoop // 跳出外层循环
}
print("i=\(i), j=\(j)")
}
}
函数:定义、参数与返回值
函数是代码复用的最小单元。Swift 函数的定义语法如下:
func 函数名(参数标签 参数名: 类型) -> 返回类型 {
// 函数体
return 返回值
}
Swift 函数的参数系统非常灵活,包含以下特性:
- 参数标签:每个参数可以有两个名称——外部标签(调用时使用)和内部名称(函数体内使用)。如果不想让调用者看到标签,可以用下划线
_省略。 - 默认参数值:可以为参数指定默认值,调用时可省略。
- 可变参数:接受不确定数量的同类型参数,用
...表示。 - 输入输出参数(inout):允许函数修改外部变量的值,类似于指针传递。
来看一个综合示例:
func calculateTotal(price: Double, quantity: Int, discount: Double = 0.0) -> Double {
let subtotal = price * Double(quantity)
let discounted = subtotal * (1 - discount)
return discounted
}
// 调用方式
let total1 = calculateTotal(price: 99.0, quantity: 2)
let total2 = calculateTotal(price: 99.0, quantity: 2, discount: 0.1)
函数也可以作为参数传递或作为返回值,这被称为函数类型。例如:
func add(_ a: Int, _ b: Int) -> Int { a + b }
func multiply(_ a: Int, _ b: Int) -> Int { a * b }
func applyOperation(_ op: (Int, Int) -> Int, _ x: Int, _ y: Int) -> Int {
return op(x, y)
}
let result = applyOperation(add, 5, 3) // 8
let result2 = applyOperation(multiply, 5, 3) // 15
这种高阶函数的特性,为后续学习闭包(Closure)和函数式编程奠定了基础。
案例拆解:构建一个简易的购物车结算系统
为了将上述知识点串联起来,我们设计一个简易的购物车结算场景。假设一个电商 App 需要计算购物车中商品的总价,并应用会员折扣和满减优惠。
需求约束:
- 商品包含名称、单价和数量。
- 会员折扣:普通会员 95 折,VIP 会员 8 折。
- 满减规则:总价满 200 元减 20 元,满 500 元减 80 元。
- 结算结果需要显示原价、优惠明细和最终应付金额。
代码实现:
struct Product {
let name: String
let price: Double
let quantity: Int
}
enum Membership {
case normal, silver, vip
}
func calculateCartTotal(products: [Product], membership: Membership) -> (original: Double, discount: Double, final: Double) {
// 1. 计算原价
var originalTotal = 0.0
for product in products {
originalTotal += product.price * Double(product.quantity)
}
// 2. 会员折扣
var discountRate = 1.0
switch membership {
case .normal:
discountRate = 1.0
case .silver:
discountRate = 0.95
case .vip:
discountRate = 0.80
}
let afterMemberDiscount = originalTotal * discountRate
// 3. 满减优惠
var reduction = 0.0
if afterMemberDiscount >= 500 {
reduction = 80.0
} else if afterMemberDiscount >= 200 {
reduction = 20.0
}
let finalTotal = afterMemberDiscount - reduction
let totalDiscount = originalTotal - finalTotal
return (originalTotal, totalDiscount, finalTotal)
}
// 测试数据
let cart = [
Product(name: "iPhone 15", price: 5999.0, quantity: 1),
Product(name: "AirPods Pro", price: 1899.0, quantity: 1),
Product(name: "充电器", price: 149.0, quantity: 2)
]
let result = calculateCartTotal(products: cart, membership: .vip)
print("原价:\(result.original) 元")
print("总优惠:\(result.discount) 元")
print("应付:\(result.final) 元")
运行结果:
原价:8196.0 元
总优惠:1639.2 元
应付:6556.8 元
复盘分析:这个案例综合运用了结构体(数据类型)、枚举(数据类型)、数组(集合类型)、for-in 循环(控制流)、switch(控制流)、if-else(控制流)、函数定义与元组返回值。通过这个案例,你可以看到基础语法如何协同工作来解决实际问题。值得注意的是,originalTotal 使用了 var 因为需要累加;而 discountRate 和 reduction 使用了 let 因为一旦计算完成就不再变化。这种选择体现了 Swift 的编程哲学。
常见误区与总结复盘
初学者在学习 Swift 基础语法时,常犯以下错误:
有些学员习惯将所有声明都写成 var,认为这样更灵活。但这样做不仅降低了代码可读性,还可能在多线程环境中引入数据竞争。正确的做法是:默认使用 let,只有当值确实需要被修改时才改用 var。
在 Swift 中,Int 和 Double 之间的运算必须显式转换。例如
let result = 3 + 0.14 会报错,必须写成 let result = Double(3) + 0.14。很多初学者在混合运算时容易忽略这一点。
虽然 Swift 的 switch 不需要显式 break,但如果枚举或整数类型没有覆盖所有可能情况,编译器会强制要求添加 default 分支。否则代码无法编译通过。
总结本章,Swift 的基础语法虽然看似简单,但每一个细节都体现了语言设计者对安全性和表达力的追求。变量与常量的选择、类型的严格管理、控制流的灵活性、函数的参数体系,这些都不是孤立的语法点,而是相互关联的整体。建议你在学习过程中,多动手编写小实验代码,例如修改购物车案例中的折扣规则,或者添加新的商品类型,通过实际运行来验证你的理解。只有将语法内化为思维习惯,才能在后续的 UI 开发、网络请求、数据持久化等高级主题中游刃有余。