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 教程目录

✈️💬