Swift 编程:函数嵌套、类与结构体详解
1. 函数嵌套
在 Swift 编程中,函数嵌套是一种非常实用的技巧。之前我们看到的函数大多是全局函数,全局函数定义在类或文件的全局作用域内。而 Swift 允许我们在一个函数中嵌套另一个函数。
嵌套函数只能在包含它的函数内部被调用,但包含它的函数可以返回这个嵌套函数,从而使其在包含函数的作用域之外使用。
下面通过一个简单的排序函数示例来展示如何嵌套函数:
func sort(inout numbers: [Int]) {
// 这是嵌套函数
func reverse(inout first: Int, inout second: Int) {
let tmp = first
first = second
second = tmp
}
// 嵌套函数结束
var count = numbers.count
while count > 0 {
for var i = 1; i < count; i++ {
if numbers[i] < numbers[i-1] {
reverse(&numbers[i], second: &numbers[i-1])
}
}
count--
}
}
在上述代码中,我们首先创建了一个名为
sort
的全局函数,它接受一个
inout
参数,即一个整数数组。在
sort
函数内部,我们定义了一个名为
reverse
的嵌套函数,该函数用于交换两个传入的值。
在
sort
函数的主体中,我们实现了简单排序的逻辑。在这个逻辑中,我们比较数组中的两个数字,如果需要交换它们的位置,就调用嵌套的
reverse
函数。
以下是调用全局
sort
函数的示例:
var nums: [Int] = [6,2,5,3,1]
sort(&nums)
for value in nums {
print("--\(value)")
}
这段代码创建了一个包含五个整数的数组,然后将该数组传递给
sort
函数。当
sort
函数返回时,
nums
数组将包含一个已排序的数组。
需要注意的是,嵌套函数虽然很有用,但也容易被过度使用。在创建嵌套函数之前,你应该问自己为什么要使用嵌套函数,以及使用它能解决什么问题。
2. 验证 IPv4 地址的函数
为了巩固所学知识,我们再看一个示例。这里我们将创建一个函数,用于测试一个字符串值是否包含有效的 IPv4 地址。
IPv4 地址是分配给使用互联网协议进行通信的计算机的地址,它由四个范围在 0 - 255 之间的数字组成,用点号分隔。例如,
10.0.1.250
就是一个有效的 IP 地址。
func isValidIP(ipAddr: String?) -> Bool {
guard let ipAddr = ipAddr else {
return false
}
let octets = ipAddr.characters.split { $0 == "."}.map{String($0)}
guard octets.count == 4 else {
return false
}
func validOctet(octet: String) -> Bool {
guard let num = Int(String(octet))
where num >= 0 && num < 256 else {
return false
}
return true
}
for octet in octets {
guard validOctet(octet) else {
return false
}
}
return true
}
由于
isValidIP()
函数的参数是可选类型,我们首先要验证
ipAddr
参数是否为
nil
。为此,我们使用了带有可选绑定的
guard
语句,如果可选绑定失败,我们返回布尔值
false
,因为
nil
不是有效的 IP 地址。
如果
ipAddr
参数包含非
nil
值,我们将字符串按点号分割成一个字符串数组。由于 IP 地址应该包含四个用点号分隔的数字,我们使用
guard
语句检查数组是否包含四个元素。如果不包含,我们返回
false
,因为我们知道
ipAddr
参数不包含有效的 IP 地址。
接下来,我们创建了一个名为
validOctet()
的嵌套函数,它接受一个名为
octet
的字符串参数。这个嵌套函数将验证
octet
参数是否包含一个介于 0 和 255 之间的数字,如果是,则返回布尔值
true
,否则返回
false
。
最后,我们遍历通过分割原始
ipAddr
参数得到的数组中的值,并将这些值传递给
validOctet()
嵌套函数。如果所有四个值都通过了
validOctet()
函数的验证,我们就认为这是一个有效的 IP 地址,并返回布尔值
true
;否则,我们返回
false
。
3. 类与结构体概述
在 Swift 中,类和结构体是非常相似的概念,但也有一些重要的区别。理解它们的相似之处和不同之处对于掌握 Swift 编程至关重要,因为它们是应用程序的构建块。
3.1 类和结构体的相似点
- 属性 :用于在类和结构体中存储信息。
- 方法 :为类和结构体提供功能。
- 初始化器 :用于初始化类和结构体的实例。
- 下标 :使用下标语法访问值。
- 扩展 :帮助扩展类和结构体的功能。
3.2 类和结构体的不同点
| 比较项 | 结构体 | 类 |
|---|---|---|
| 类型 | 值类型 | 引用类型 |
| 继承 | 不能从其他类型继承 | 可以继承 |
| 析构器 | 不能有自定义析构器 | 可以有自定义析构器 |
| 多引用 | 不能有多个引用 | 可以有多个引用 |
4. 值类型与引用类型
结构体(如枚举和元组)是值类型,这意味着当我们在应用程序中传递结构体的实例时,我们传递的是结构体的副本,而不是原始结构体。类是引用类型,这意味着当我们在应用程序中传递类的实例时,我们传递的是对原始实例的引用。
为了说明值类型和引用类型的区别,我们可以用一本书来举例。如果我们的朋友想读《Mastering Swift》这本书,我们可以给他们买一本新的,也可以和他们分享我们自己的那本。
如果我们给朋友买了一本新的,他们在书中做的任何笔记都只会留在他们的那本中,不会影响我们的那本。这就像值类型的传递,函数得到的是结构体的副本,对副本的修改不会影响原始结构体。
如果我们和朋友分享我们的那本,他们在书中做的笔记在归还时会留在书中。这就像引用类型的传递,函数得到的是对原始实例的引用,对实例的修改会保留下来。
5. 创建类和结构体
我们使用相同的语法来定义类和结构体,唯一的区别是使用
class
关键字定义类,使用
struct
关键字定义结构体。
class MyClass {
// MyClass 定义
}
struct MyStruct {
// MyStruct 定义
}
在上述代码中,我们定义了一个名为
MyClass
的新类和一个名为
MyStruct
的新结构体。这实际上创建了两个新的 Swift 类型。在命名新类型时,我们应该遵循 Swift 的标准命名约定,即使用驼峰命名法,首字母大写。类或结构体中定义的任何方法或属性也应该使用驼峰命名法,首字母小写。
6. 属性
属性将值与类或结构体关联起来,主要有两种类型:
-
存储属性
:作为类或结构体实例的一部分存储变量或常量值。存储属性可以有属性观察器,用于监控属性的变化并在值改变时执行自定义操作。
-
计算属性
:本身不存储值,而是检索和可能设置其他属性。计算属性返回的值也可以在请求时进行计算。
6.1 存储属性
存储属性是存储在类或结构体实例中的变量或常量。我们可以为存储属性提供默认值,它们使用
var
关键字定义。
struct MyStruct {
let c = 5
var v = ""
}
class MyClass {
let c = 5
var v = ""
}
从这个例子可以看出,定义存储属性的语法对于类和结构体是相同的。以下是创建结构体和类实例的示例:
var myStruct = MyStruct()
var myClass = MyClass()
结构体和类的一个区别是,默认情况下,结构体创建一个初始化器,允许我们在创建结构体实例时填充存储属性。例如:
var myStruct = MyStruct(v: "Hello")
在这个例子中,初始化器用于设置变量
v
,常量
c
将包含在结构体中设置的数字 5。如果我们没有给常量一个初始值,默认初始化器也会用于设置常量。
struct MyStruct {
let c: Int
var v = ""
}
var myStruct = MyStruct(c: 10, v: "Hello")
这允许我们在运行时初始化类或结构体时设置常量的值,而不是在代码中硬编码常量的值。
初始化器中参数的顺序与我们定义它们的顺序相同。在前面的例子中,我们首先定义了常量
c
,所以它是初始化器中的第一个参数;我们第二个定义了变量
v
,所以它是初始化器中的第二个参数。
要设置或读取存储属性,我们使用标准的点语法。例如:
var x = myClass.c
myClass.v = "Howdy"
在继续介绍计算属性之前,我们创建一个表示员工的结构体和类:
struct EmployeeStruct {
var firstName = ""
var lastName = ""
var salaryYear = 0.0
}
public class EmployeeClass {
var firstName = ""
var lastName = ""
var salaryYear = 0.0
}
这个员工结构体名为
EmployeeStruct
,员工类名为
EmployeeClass
。类和结构体都有三个存储属性:
firstName
、
lastName
和
salaryYear
。
在结构体或类内部,我们可以使用属性名或
self
关键字来访问这些属性。每个结构体或类的实例都有一个名为
self
的属性,它指的是实例本身。以下是使用
self
关键字在结构体或类实例中访问属性的示例:
self.firstName = "Jon"
self.lastName = "Hoffman"
7. 计算属性
计算属性没有用于存储与属性关联的值的后端变量,其值通常在代码请求时进行计算。你可以将计算属性看作是伪装成属性的函数。
7.1 只读计算属性
以下是定义只读计算属性的示例:
var salaryWeek: Double {
get{
return self.salaryYear/52
}
}
要创建只读计算属性,我们首先像定义普通变量一样使用
var
关键字,后面跟着变量名、冒号和变量类型。不同的是,我们在声明的末尾添加一个花括号,然后定义一个 getter 方法,当请求计算属性的值时会调用这个方法。在这个例子中,getter 方法将
salaryYear
属性的当前值除以 52,得到员工的周薪。
我们可以通过移除
get
关键字来简化只读计算属性的定义:
var salaryWeek: Double {
return self.salaryYear/52
}
7.2 可写计算属性
计算属性并不局限于只读,我们也可以对其进行写入操作。为了使
salaryWeek
属性可写,我们需要添加一个 setter 方法。
var salaryWeek: Double {
get {
return self.salaryYear/52
}
set (newSalaryWeek){
self.salaryYear = newSalaryWeek*52
}
}
我们可以通过不定义新值的名称来简化 setter 定义。在这种情况下,值将被分配给默认变量名
newValue
。
var salaryWeek: Double {
get{
return self.salaryYear/52
}
set{
self.salaryYear = newValue*52
}
}
上述定义的
salaryWeek
计算属性可以添加到
EmployeeClass
类或
EmployeeStruct
结构体中,而无需进行任何修改。以下是将
salaryWeek
属性添加到
EmployeeClass
类的示例:
public class EmployeeClass {
var firstName = ""
var lastName = ""
var salaryYear = 0.0
var salaryWeek: Double {
get{
return self.salaryYear/52
}
set (newSalaryWeek){
self.salaryYear = newSalaryWeek*52
}
}
}
以下是将
salaryWeek
计算属性添加到
EmployeeStruct
结构体的示例:
struct EmployeeStruct {
var firstName = ""
var lastName = ""
var salaryYear = 0.0
var salaryWeek: Double {
get{
return self.salaryYear/52
}
set (newSalaryWeek){
// 这里原文档未完整,推测是实现与类中相同的逻辑
self.salaryYear = newSalaryWeek*52
}
}
}
通过以上内容,我们详细介绍了 Swift 中的函数嵌套、类和结构体的相关知识,包括它们的定义、使用方法以及相互之间的区别。掌握这些知识将有助于我们编写更加高效、可维护的 Swift 代码。
Swift 编程:函数嵌套、类与结构体详解
8. 初始化器
初始化器用于在创建类或结构体的实例时进行必要的设置。在 Swift 中,类和结构体都可以有初始化器。
结构体默认会为其存储属性提供一个成员逐一初始化器,允许我们在创建实例时为每个存储属性赋值。例如,对于之前定义的
EmployeeStruct
:
struct EmployeeStruct {
var firstName = ""
var lastName = ""
var salaryYear = 0.0
}
let employeeStruct = EmployeeStruct(firstName: "John", lastName: "Doe", salaryYear: 50000.0)
对于类,我们需要自己定义初始化器。初始化器使用
init
关键字定义。以下是为
EmployeeClass
定义初始化器的示例:
public class EmployeeClass {
var firstName: String
var lastName: String
var salaryYear: Double
init(firstName: String, lastName: String, salaryYear: Double) {
self.firstName = firstName
self.lastName = lastName
self.salaryYear = salaryYear
}
}
let employeeClass = EmployeeClass(firstName: "Jane", lastName: "Smith", salaryYear: 60000.0)
在初始化器中,我们使用
self
关键字来区分实例属性和初始化器参数。
9. 访问控制
访问控制用于限制代码中实体的访问级别,确保代码的安全性和封装性。Swift 提供了几种访问控制级别:
-
开放(open)
:最高的访问级别,允许在模块内和模块外访问和继承。
-
公开(public)
:允许在模块内和模块外访问,但不允许在模块外继承。
-
内部(internal)
:默认的访问级别,允许在模块内访问。
-
文件私有(fileprivate)
:限制访问仅限于定义该实体的文件。
-
私有(private)
:限制访问仅限于定义该实体的作用域。
以下是一个使用访问控制的示例:
// 公开类
public class PublicClass {
// 公开属性
public var publicProperty = 0
// 内部属性(默认)
var internalProperty = 1
// 文件私有属性
fileprivate var filePrivateProperty = 2
// 私有属性
private var privateProperty = 3
}
// 内部类
class InternalClass {
// ...
}
// 文件私有类
fileprivate class FilePrivateClass {
// ...
}
// 私有类
private class PrivateClass {
// ...
}
在使用访问控制时,我们需要根据实际需求选择合适的访问级别,以保护代码的安全性和封装性。
10. 类的继承
类可以继承其他类的属性和方法,这是类与结构体的一个重要区别。继承允许我们创建类的层次结构,提高代码的复用性。
以下是一个简单的类继承示例:
// 基类
class Person {
var name: String
init(name: String) {
self.name = name
}
func introduce() {
print("My name is \(name).")
}
}
// 子类
class Employee: Person {
var salary: Double
init(name: String, salary: Double) {
self.salary = salary
super.init(name: name)
}
override func introduce() {
print("My name is \(name), and my salary is \(salary).")
}
}
let employee = Employee(name: "Tom", salary: 55000.0)
employee.introduce()
在这个示例中,
Employee
类继承自
Person
类。在子类的初始化器中,我们需要先初始化子类的属性,然后调用父类的初始化器。使用
override
关键字可以重写父类的方法。
11. 扩展
扩展允许我们为现有的类、结构体、枚举或协议添加新的功能,而无需修改原始定义。扩展可以添加计算属性、方法、初始化器等。
以下是一个扩展
EmployeeClass
的示例:
extension EmployeeClass {
func getMonthlySalary() -> Double {
return salaryYear / 12
}
}
let emp = EmployeeClass(firstName: "Alice", lastName: "Johnson", salaryYear: 72000.0)
print("Monthly salary: \(emp.getMonthlySalary())")
通过扩展,我们为
EmployeeClass
添加了一个新的方法
getMonthlySalary
,用于计算员工的月工资。
12. 内存管理与 ARC
在 Swift 中,使用自动引用计数(ARC)来管理内存。ARC 会自动跟踪和管理类实例的引用计数,当一个类实例的引用计数为 0 时,ARC 会自动释放该实例占用的内存。
结构体和枚举是值类型,它们不使用引用计数,而是在传递时进行复制。
以下是一个简单的示例来说明 ARC 的工作原理:
class MyClass {
init() {
print("MyClass instance created.")
}
deinit {
print("MyClass instance deallocated.")
}
}
var reference1: MyClass? = MyClass()
var reference2 = reference1
var reference3 = reference1
reference1 = nil
reference2 = nil
reference3 = nil
在这个示例中,当
reference1
、
reference2
和
reference3
都被设置为
nil
时,
MyClass
实例的引用计数变为 0,ARC 会自动调用
deinit
方法释放内存。
小结
通过本文,我们全面了解了 Swift 编程中的函数嵌套、类与结构体的相关知识。函数嵌套可以帮助我们组织代码,提高代码的可读性和可维护性。类和结构体是 Swift 中重要的编程概念,它们有相似之处,也有明显的区别。我们学习了如何创建类和结构体,添加属性、方法、初始化器,使用访问控制、继承和扩展,以及如何利用 ARC 进行内存管理。掌握这些知识将使我们能够编写更加高效、安全和可维护的 Swift 代码。在实际编程中,我们需要根据具体需求选择合适的编程结构和技术,以实现最佳的编程效果。
下面是一个简单的 mermaid 流程图,展示了创建类实例并调用方法的流程:
graph TD;
A[开始] --> B[定义类];
B --> C[创建类实例];
C --> D[调用类方法];
D --> E[结束];
通过这个流程图,我们可以清晰地看到创建类实例并调用方法的基本流程。希望本文对你学习 Swift 编程有所帮助!
109

被折叠的 条评论
为什么被折叠?



