You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中if语句变量使用报错及代码优化与枚举添加咨询

问题解决与代码优化指南

一、先解决取消注释后的报错问题

你取消注释后代码报错的核心原因是:doomsdayPerMonth变量被定义在if/else代码块内部,块外的代码无法访问到它——Swift的变量作用域是块级的,所以必须把变量声明移到块外,再在块内完成赋值。

修改后的这部分代码应该是这样:

// 先在块外声明变量并指定类型
var doomsdayPerMonth: [Int: Int]
if remainder == 0 { 
    doomsdayPerMonth = [1:31, 2:28, 3:7, 4:4, 5:9, 6:6, 7:11, 8:8, 9:5, 10:10, 11:7, 12:12] 
} else { 
    doomsdayPerMonth = [1:25, 2:29, 3:7, 4:4, 5:9, 6:6, 7:11, 8:8, 9:5, 10:10, 11:7, 12:12] 
}

不过这里要提一句:你原来用remainder == 0(年份减1900后是12的倍数)来区分平闰年的逻辑是错误的,这也是1、2月计算出错的根源,后面会详细修正。

二、修复1、2月的计算问题(Doomsday算法的正确逻辑)

Doomsday算法中,1、2月的基准日是由年份是否为闰年决定的,和年份是否是12的倍数无关。针对1900-1999年的日期:

  • 平年(不能被4整除):1月Doomsday是31日,2月是28日
  • 闰年(能被4整除):1月Doomsday是31日,2月是29日

你原来else块里把1月的Doomsday写成25是完全错误的,这才是1月计算出错的关键。修正后的判断逻辑如下:

var doomsdayPerMonth: [Int: Int]
// 1900年代的闰年判断:能被4整除的年份(1900不是闰年,但我们处理的是1900年代出生者,即1901-1999,直接用year%4==0即可)
if year % 4 != 0 { 
    // 平年
    doomsdayPerMonth = [1:31, 2:28, 3:7, 4:4, 5:9, 6:6, 7:11, 8:8, 9:5, 10:10, 11:7, 12:12] 
} else { 
    // 闰年
    doomsdayPerMonth = [1:31, 2:29, 3:7, 4:4, 5:9, 6:6, 7:11, 8:8, 9:5, 10:10, 11:7, 12:12] 
}

这样1、2月的计算就能回归正确了。

三、为日、月、年参数添加枚举类型

1. 月份枚举(最适合用枚举约束)

在Swift里,我们可以定义一个Month枚举,把每个月份对应起来,这样函数参数只能传入合法的月份,从根源避免无效值:

enum Month: Int, CaseIterable {
    case january = 1
    case february = 2
    case march = 3
    case april = 4
    case may = 5
    case june = 6
    case july = 7
    case august = 8
    case september = 9
    case october = 10
    case november = 11
    case december = 12
    
    // 直接添加获取月份名称的属性,避免重复定义数组
    var name: String {
        switch self {
        case .january: return "January"
        case .february: return "February"
        case .march: return "March"
        case .april: return "April"
        case .may: return "May"
        case .june: return "June"
        case .july: return "July"
        case .august: return "August"
        case .september: return "September"
        case .october: return "October"
        case .november: return "November"
        case .december: return "December"
        }
    }
}

2. 日期的处理(枚举不适合,用类型约束+验证)

日期范围是1-31,但不同月份的最大天数不同,用枚举会非常繁琐。更合理的方式是用UInt8类型(最大取值255,完全覆盖日期范围),然后在函数内部添加参数验证,确保传入的日期是对应月份的合法天数:

// 在findDayOfWeek函数开头添加验证逻辑
let maxDay: Int
switch month {
case .april, .june, .september, .november: maxDay = 30
case .february: maxDay = year % 4 == 0 ? 29 : 28
default: maxDay = 31
}
guard let dayInt = Int(exactly: day), dayInt >= 1 && dayInt <= maxDay else {
    fatalError("Invalid day \(day) for \(month.name) \(year)")
    // 也可以返回可选值(比如-> String?),根据你的需求选择
}

3. 修改函数参数类型

把原来的函数参数改成枚举和约束后的类型,提升代码可读性和安全性:

func findDayOfWeek(month: Month, day: UInt8, year: Int) -> String {
    // 先做参数验证
    let maxDay: Int
    switch month {
    case .april, .june, .september, .november: maxDay = 30
    case .february: maxDay = year % 4 == 0 ? 29 : 28
    default: maxDay = 31
    }
    guard let dayInt = Int(exactly: day), dayInt >= 1 && dayInt <= maxDay else {
        fatalError("Invalid day \(day) for \(month.name) \(year)")
    }
    // 后续逻辑用dayInt代替day
    // ... 原来的核心计算代码
}

调用时需要传入枚举值,可读性大幅提升:

print(personBday.findDayOfWeek(month: .march, day: 24, year: 1914))

四、编码习惯优化建议

这些建议能帮你避免不良习惯,让代码更健壮、易读:

  • 去掉强制解包(!):原代码里的doomsdayOfMonth!、months[month]!都是高危操作,一旦key不存在就会崩溃。应该用可选绑定替代:
    guard let doomsdayOfMonth = doomsdayPerMonth[month.rawValue] else {
        fatalError("Invalid month: \(month)")
    }
    
  • 提取重复常量:原代码两个函数都定义了月份名称数组,现在可以直接用Month枚举的name属性,避免重复代码。
  • 优化参数命名:原函数省略了外部参数名,调用时findDayOfWeek(3,24,1914)可读性差,改成带外部参数名的形式,调用时更清晰:findDayOfWeek(month: .march, day:24, year:1914)。
  • 用数学运算替代冗余循环:原代码里的while testNumber >3和while testNumber2>7可以用数学运算简化,更高效:
    // 替换第一个while循环
    runningNumber += remainder + (remainder / 4)
    // 替换第二个while循环
    runningNumber = runningNumber % 7
    
  • 封装重复逻辑:把日期验证、Doomsday数组生成等重复逻辑封装成私有函数,让主函数更简洁。
  • 添加有意义的注释:原注释都是打印过程记录,应该添加注释解释为什么要做这些计算(比如Doomsday算法的步骤),而不是做了什么。

完整优化后的代码

enum Month: Int, CaseIterable {
    case january = 1
    case february = 2
    case march = 3
    case april = 4
    case may = 5
    case june = 6
    case july = 7
    case august = 8
    case september = 9
    case october = 10
    case november = 11
    case december = 12
    
    var name: String {
        switch self {
        case .january: return "January"
        case .february: return "February"
        case .march: return "March"
        case .april: return "April"
        case .may: return "May"
        case .june: return "June"
        case .july: return "July"
        case .august: return "August"
        case .september: return "September"
        case .october: return "October"
        case .november: return "November"
        case .december: return "December"
        }
    }
}

class Birthday {
    private let daysOfWeek: [Int: String] = [
        0: "Wednesday", 
        1: "Thursday", 
        2: "Friday", 
        3: "Saturday", 
        4: "Sunday", 
        5: "Monday", 
        6: "Tuesday"
    ]
    
    func findDayOfWeek(month: Month, day: UInt8, year: Int) -> String {
        // 验证年份范围(1900年代)
        guard year >= 1900 && year <= 1999 else {
            fatalError("This function only handles years from 1900 to 1999")
        }
        
        // 验证日期合法性
        let maxDay: Int
        switch month {
        case .april, .june, .september, .november: maxDay = 30
        case .february: maxDay = year % 4 == 0 ? 29 : 28
        default: maxDay = 31
        }
        guard let dayInt = Int(exactly: day), dayInt >= 1 && dayInt <= maxDay else {
            fatalError("Invalid day \(day) for \(month.name) \(year)")
        }
        
        // Doomsday算法核心计算
        let shortenedYear = year - 1900
        let remainder = shortenedYear % 12
        var runningNumber = remainder + (remainder / 4)
        let dividedByTwelve = (shortenedYear - remainder) / 12
        runningNumber += dividedByTwelve
        
        // 取模7简化数值
        runningNumber = runningNumber % 7
        
        // 获取对应月份的Doomsday
        let doomsdayPerMonth: [Int: Int]
        if year % 4 != 0 {
            doomsdayPerMonth = [1:31, 2:28, 3:7, 4:4, 5:9, 6:6, 7:11, 8:8, 9:5, 10:10, 11:7, 12:12]
        } else {
            doomsdayPerMonth = [1:31, 2:29, 3:7, 4:4, 5:9, 6:6, 7:11, 8:8, 9:5, 10:10, 11:7, 12:12]
        }
        
        guard let doomsdayOfMonth = doomsdayPerMonth[month.rawValue] else {
            fatalError("Invalid month: \(month)")
        }
        
        var daysUntilBDay = dayInt - doomsdayOfMonth
        while daysUntilBDay < 0 {
            daysUntilBDay += 7
        }
        runningNumber += daysUntilBDay % 7
        runningNumber = runningNumber % 7
        
        guard let dayName = daysOfWeek[runningNumber] else {
            fatalError("Invalid day index: \(runningNumber)")
        }
        
        return "\(month.name) \(dayInt), \(year) was a \(dayName)."
    }
    
    func writeInWords(month: Month, day: UInt8, year: Int) -> String {
        // 复用日期验证逻辑
        let maxDay: Int
        switch month {
        case .april, .june, .september, .november: maxDay = 30
        case .february: maxDay = year % 4 == 0 ? 29 : 28
        default: maxDay = 31
        }
        guard let dayInt = Int(exactly: day), dayInt >= 1 && dayInt <= maxDay else {
            fatalError("Invalid day \(day) for \(month.name) \(year)")
        }
        return "\(month.name) \(dayInt), \(year)"
    }
}

let personBday = Birthday()
print(personBday.findDayOfWeek(month: .march, day: 24, year: 1914))
// 输出:March 24, 1914 was a Tuesday.

内容的提问来源于stack exchange,提问作者JWIDROFF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:58:06