Swift 练习题(15 题)
Swift 学了语法不动手写等于白学。这一页给 15 道精选练习题,按难度分三级——基础 6 题(变量 / 运算 / 控制流 / 函数)、进阶 5 题(重点攻克 optional 与 enum 关联值)、实战 4 题(综合应用)。每题都含题目描述、提示、可折叠的参考答案。
使用建议:先别急着展开答案。读题后自己打开 Swift Playgrounds、Xcode 或 在线 Swift 运行环境试着写,卡住了再看提示,实在写不出再对照参考答案。Swift 答案不止一种,只要能编译通过、可读性强就是好代码。
基础(6 题):变量 / 运算 / 控制流 / 函数
这一组覆盖 Swift 最基础的语言元素,目标是用最短的代码解决一个小问题,训练"把思路翻译成代码"的能力,同时熟悉 for-in、guard、func 等高频语法。
第 1 题:翻转字符串
题目:写一个函数 reverseString(_ s: String) -> String,把字符串翻转过来。比如 "hello" 翻转成 "olleh"。
提示:Swift 的 String.reversed() 返回倒序字符序列,需要再用 String(_:) 包回字符串;也可以转 Array(s) 后用 swapAt 双指针。
参考答案
// 方法 1:reversed() 返回倒序序列,再用 String 拼回字符串
func reverseString(_ s: String) -> String {
return String(s.reversed())
}
// 方法 2:转数组双指针手动翻转(理解原理)
func reverseString2(_ s: String) -> String {
var arr = Array(s)
var i = 0, j = arr.count - 1
while i < j {
arr.swapAt(i, j)
i += 1; j -= 1
}
return String(arr)
}
print(reverseString("hello")) // olleh
print(reverseString("Swift")) // tfiwS
print(reverseString2("你好世界")) // 界世好你
// 输出: olleh第 2 题:判断素数
题目:写一个函数 isPrime(_ n: Int) -> Bool,判断正整数 n 是否为素数。
提示:小于 2 的都不是素数;只需检查从 2 到 sqrt(n)——超过平方根的因子必然配对一个已经检查过的更小因子。Swift 用 Double(n).squareRoot() 取平方根。
参考答案
func isPrime(_ n: Int) -> Bool {
if n < 2 { return false }
// 只需检查到 sqrt(n):超过平方根的因子必然配对一个更小的、已检查过的因子
let limit = Int(Double(n).squareRoot())
for i in 2...limit {
if n % i == 0 { return false }
}
return true
}
print(isPrime(2)) // true
print(isPrime(7)) // true
print(isPrime(10)) // false
print(isPrime(1)) // false
// 输出: true第 3 题:FizzBuzz
题目:打印 1 到 15。3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数打印 FizzBuzz,其余打印数字本身。
提示:if / else if / else 的判断顺序很关键——必须先判断"同时是 3 和 5 的倍数"(即 15 的倍数),否则会被前面的分支截胡。Swift 区间用 1...15。
参考答案
// 经典题:3 的倍数 Fizz,5 的倍数 Buzz,15 的倍数 FizzBuzz,其余打印数字
for i in 1...15 {
if i % 15 == 0 { // 必须先判断 15(否则会被 3 或 5 截胡)
print("FizzBuzz")
} else if i % 3 == 0 {
print("Fizz")
} else if i % 5 == 0 {
print("Buzz")
} else {
print(i)
}
}
// 输出(节选):
// 1
// 2
// Fizz
// 4
// Buzz
// Fizz
// 7
// ...
// 14
// FizzBuzz第 4 题:数组最大值
题目:不使用内置 .max(),写一个函数 findMax(_ arr: [Int]) -> Int?,返回数组中的最大值;空数组返回 nil。
提示:Swift 数组的 dropFirst() 返回去掉首个元素后的切片;遍历比较、遇到更大的就更新。Swift 的 Int? 自动表达"可能为 nil"。
参考答案
// 方法 1:手写遍历(空数组返回 nil,所以返回 Int?)
func findMax(_ arr: [Int]) -> Int? {
guard !arr.isEmpty else { return nil }
var m = arr[0]
for x in arr.dropFirst() { // 从第二个开始比较
if x > m { m = x }
}
return m
}
// 方法 2:实际项目直接用 .max()(返回 Int?)
print(findMax([3, 1, 4, 1, 5, 9, 2, 6])!) // 9
print([3, 1, 4, 1, 5, 9, 2, 6].max()!) // 9
print(findMax([]) ?? "nil") // nil
// 输出: 9第 5 题:数组求和
题目:给定一个整数数组,返回所有元素的和。至少写两种写法。
提示:Swift 的 reduce(_:_:) 第一个参数是初始值,第二个是合并函数(直接传 + 也行);reduce(into:) 适合可变累加场景。
参考答案
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 方法 1:reduce(_:_:) 把初始值 0 与每个元素用 + 合并
let total = nums.reduce(0, +)
// 方法 2:reduce(into:) 性能更好(大数组或引用类型时优先)
let total2 = nums.reduce(into: 0) { $0 += $1 }
// 方法 3:传统 for 循环
var sum = 0
for x in nums { sum += x }
print(total) // 55
print(total2) // 55
print(sum) // 55
// 输出: 55第 6 题:计算阶乘
题目:写一个函数 factorial(_ n: Int) -> Int,返回 n!。要求 factorial(0) == 1,且对负数调用 fatalError。
提示:循环写法用累乘变量;递归写法更贴近数学定义 n! = n * (n-1)!。1...n 是闭区间,空区间要用 if n > 0 保护。
参考答案
// 方法 1:循环
func factorial(_ n: Int) -> Int {
guard n >= 0 else { fatalError("阶乘不接受负数") }
var result = 1
if n > 0 {
for i in 1...n { result *= i }
}
return result
}
// 方法 2:递归(更贴近数学定义 n! = n * (n-1)!)
func factorialRec(_ n: Int) -> Int {
guard n >= 0 else { fatalError("阶乘不接受负数") }
return n < 2 ? 1 : n * factorialRec(n - 1)
}
print(factorial(5)) // 120
print(factorial(0)) // 1
print(factorialRec(6)) // 720
// 输出: 120进阶(5 题):optional / enum
这一组聚焦 Swift 最具特色的 optional 类型——它把"可能为 nil"提到类型系统层面,逼你在编译期处理空值。五个题串起 optional 的全部常用模式,最后用 enum 关联值收尾。
第 7 题:optional 与 if let 绑定
题目:声明一个 String? 类型的常量,用 if let 安全解包:解包成功打印问候语,失败走 else 分支打印"unknown"。
提示:if let n = name { ... } 解包成功后,n 在 if 分支内是 String 类型(非 optional),直接使用即可。
参考答案
// optional:可能为 nil 的值,必须解包才能使用
let name: String? = "Alice"
let age: Int? = nil
// if let 绑定:解包成功进入 if 分支,常量 n 是非 optional 的 String
if let n = name {
print("Hello, \(n)!") // n 是 String,直接可用
}
// age 为 nil,跳过分支
if let a = age {
print("Age is \(a)")
} else {
print("Age unknown")
}
// 输出: Hello, Alice!第 8 题:guard let 早返回
题目:写一个 greet(_ name: String?) 函数,用 guard let 提前解包——名字为空或 nil 时直接 return,否则打印问候语。
提示:guard let n = name, !n.isEmpty else { return };guard 解包后 n 在函数主作用域可见,代码比嵌套的 if let 更扁平。
参考答案
// guard let:提前解包,失败就 early return,让"快乐路径"留在主作用域
func greet(_ name: String?) {
guard let n = name, !n.isEmpty else {
print("名字为空,无法打招呼")
return // guard 强制要求 return / continue / break / throw
}
// 到这里 n 一定是 String,且作用域可见
print("你好,\(n)!")
}
greet("Bob") // 你好,Bob!
greet(nil) // 名字为空,无法打招呼
greet("") // 名字为空,无法打招呼
// 输出: 你好,Bob!第 9 题:optional chaining ?.
题目:定义 User,里面有 address: Address?,Address 里有 city。给两个用户(一个有地址、一个没地址),安全访问他们的 city。
提示:用 user.address?.city——遇到 nil 整条链立刻返回 nil,不崩溃;可以再叠加 ?? "未知" 给默认值。
参考答案
struct Address { var city: String }
struct User { var address: Address? }
let alice = User(address: Address(city: "北京"))
let bob = User(address: nil)
// optional chaining:遇到 nil 整条链立刻返回 nil,不会崩溃
print(alice.address?.city) // Optional("北京")
print(bob.address?.city) // nil
// 配合 ?? 给默认值,链式写法非常优雅
let city = bob.address?.city ?? "未知"
print(city) // 未知
// 输出: Optional("北京")第 10 题:nil 合并运算符 ??
题目:给定 nickname: String? = nil 和 realname: String? = "Alice",用 ?? 链式取第一个非 nil 值,都为 nil 时取 "匿名"。
提示:a ?? b 等价于 a != nil ? a! : b;链式 a ?? b ?? c 从左到右找第一个非 nil。
参考答案
// ?? :optional 为 nil 时取右侧默认值,等价于 a != nil ? a! : b
let nickname: String? = nil
let realname: String? = "Alice"
let display1 = nickname ?? "匿名"
// 链式:从左到右找第一个非 nil
let display2 = nickname ?? realname ?? "匿名"
print(display1) // 匿名
print(display2) // Alice
// 输出: 匿名第 11 题:enum 关联值 + switch
题目:定义 enum Result 两个 case:success(String) 和 failure(code: Int, message: String)。用 switch 分别解包并打印。
提示:case 里用 let 绑定关联值,如 case .success(let data);switch 必须 exhaustive(穷尽所有 case)。
参考答案
// enum 关联值:让每个 case 携带不同的数据,常用于建模网络响应、状态机
enum Result {
case success(String)
case failure(code: Int, message: String)
}
func handle(_ result: Result) {
switch result {
case .success(let data):
print("成功:\(data)")
case .failure(let code, let message):
print("失败[\(code)] \(message)")
}
}
handle(.success("Hello")) // 成功:Hello
handle(.failure(code: 404, message: "Not Found")) // 失败[404] Not Found
// 输出: 成功:Hello实战(4 题):综合应用
这一组每题综合用到多个知识点——struct、计算属性、文件 I/O、enum 关联值——做出接近真实场景的小程序。建议每题完整跑一遍,再想想怎么扩展。
第 12 题:学生管理(struct + 排序)
题目:定义 struct Student(姓名 + 成绩数组 + 计算属性 average),把三个学生按均分降序输出。
提示:计算属性 var average: Double { ... } 不存储值;sorted(by:) 接受闭包,$0.average > $1.average 是降序简写。
参考答案
struct Student {
let name: String
let scores: [Double]
var average: Double { // 计算属性
scores.reduce(0, +) / Double(scores.count)
}
}
let students = [
Student(name: "小明", scores: [90, 80]),
Student(name: "小红", scores: [95, 88]),
Student(name: "小刚", scores: [70, 100]),
]
// sorted(by:) 按均分降序;$0/$1 是闭包参数简写
let ranked = students.sorted { $0.average > $1.average }
for (i, s) in ranked.enumerated() {
let avg = String(format: "%.1f", s.average)
print("\(i + 1). \(s.name) 均分 \(avg)")
}
// 输出:
// 1. 小红 均分 91.5
// 2. 小明 均分 85.0
// 3. 小刚 均分 85.0第 13 题:词频统计(Dictionary + subscript default)
题目:统计一段英文文本中每个单词的出现次数,按频次降序输出 Top 3。
提示:text.split(separator: " ") 拆词;Dictionary subscript freq[w, default: 0] += 1 在 key 不存在时先取默认 0,避免手动判空。
参考答案
let text = "the quick brown fox the lazy dog the fox"
// 拆词;Dictionary subscript 用 default 处理首次出现的 key
var freq: [String: Int] = [:]
for word in text.split(separator: " ") {
let w = String(word)
freq[w, default: 0] += 1 // key 不存在就先取默认 0,再 +1
}
// 按频次降序取 Top 3
let top3 = freq.sorted { $0.value > $1.value }.prefix(3)
for (word, count) in top3 {
print("\(word): \(count)")
}
// 输出:
// the: 3
// fox: 2
// quick: 1第 14 题:文件读写(try? / try!)
题目:写 readFile 和 writeFile 两个函数,要求:读取失败时返回 nil(不抛错),写入用原子模式。演示写入后再读回。
提示:try? 把抛出错误转换成 nil;try! 断言不抛错、否则崩溃;String(contentsOf:encoding:) 和 write(to:atomically:encoding:) 是 Foundation 的文本 I/O。
参考答案
import Foundation
// try?:失败时返回 nil,不抛错,适合"读不到就用默认值"的场景
func readFile(_ path: String) -> String? {
let url = URL(fileURLWithPath: path)
return try? String(contentsOf: url, encoding: .utf8)
}
func writeFile(_ content: String, to path: String) {
let url = URL(fileURLWithPath: path)
// atomically:true 先写临时文件再替换,避免写一半崩溃损坏原文件
try? content.write(to: url, atomically: true, encoding: .utf8)
}
// 写入 → 读回
let path = "/tmp/hello.txt"
writeFile("Hello, Swift!", to: path)
print(readFile(path) ?? "读取失败") // Hello, Swift!
// try! 危险演示:断言失败会直接崩溃,只在你 100% 确定不会失败时用
// let bad = try! String(contentsOfFile: "/not/exist", encoding: .utf8) // 会崩
// 输出: Hello, Swift!第 15 题:简易计算器(enum Op + switch)
题目:实现一个计算器,支持 + - * / 四则运算。要求:除数为 0 返回 nil;用 enum 限定运算符,而不是字符串。
提示:把运算符定义为 enum Op: String,rawValue 是符号;switch 分派运算;除零返回 nil;Op(rawValue: "+") 可从字符串构造 optional 运算符。
参考答案
// 用 enum 限定运算符,编译期就杜绝非法运算符,比字符串更安全
enum Op: String {
case add = "+", sub = "-", mul = "*", div = "/"
}
func calc(_ a: Double, _ op: Op, _ b: Double) -> Double? {
switch op {
case .add: return a + b
case .sub: return a - b
case .mul: return a * b
case .div:
guard b != 0 else { return nil } // 除零返回 nil
return a / b
}
}
print(calc(3, .add, 5)!) // 8.0
print(calc(10, .div, 4)!) // 2.5
print(calc(2, .mul, 9)!) // 18.0
print(calc(1, .div, 0)) // nil
// 从字符串构造运算符(rawValue 初始化返回 optional)
if let op = Op(rawValue: "+") {
print(calc(100, op, 200)!) // 300.0
}
// 输出: 8.0练习建议
不要只看答案。把每道题在 Swift Playgrounds 或 Xcode 亲手敲一遍,改一改输入试试边界情况(空数组、负数、超长字符串、nil)。Swift 的类型系统很严格,编译错误本身就是最好的老师。
试着扩展:做完第 12 题学生管理,能不能加上"按单科排名"?做完第 13 题词频统计,能不能从真实 .txt 文件读?做完第 15 题计算器,能不能加幂运算 ^?每个扩展都是一次小型的真实开发。
更多练习资源:刷算法去 LeetCode / Codewars;读 Apple 官方文档 The Swift Programming Language;参与开源 Swift 项目去 GitHub。
← 上一篇 Swift 实战项目
返回 Swift 教程目录