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
相关产品推荐
相关产品推荐

