二 类型与操作
1: 类型转换
有时候你由有一种格式的数据,需要把它转换成另一种格式。
var integer: Int = 100
var decimal: Double = 12.5
integer = decimal
复制代码
如果你这么做,然后在第三行出现错误,Swift会提示:
Cannot assign value of type 'Double' to type 'Int'
复制代码
有些编程语言没有那么严格,会无声地执行这样的转换。经验表明,这种无声的、自动的转换是软件bug的来源,经常会损害性能。Swift禁止将一种类型的值分配给另一种类型,并避免了这些问题。(Swift是强类型语言)
记住,计算机依靠我们程序员来告诉它们该做什么。在Swift中,这包括明确类型转换。如果你想要转换发生,你必须这么说!
不是简单的赋值,你需要说你想显式地转换类型。你这样做:
integer = Int(decimal)
复制代码
第三行上的赋值现在明确地告诉Swift,您想要从原始类型Double
转换为新类型Int
。
注意:在这种情况下,将十进制值赋给整数会导致精度的损失:整数变量最终的值是12而不是12.5。这就是为什么明确表述很重要。Swift希望确保你知道自己在做什么,并确保你在执行类型转换时可能会丢失数据。
2: 混合类型的操作符
到目前为止,您只看到运算符独立地作用于整数或双精度数。但是如果你有一个整数想要乘以一个双精度数呢?
你可以这样做:
let hourlyRate: Double = 19.5
let hoursWorked: Int = 10
let totalCost: Double = hourlyRate * hoursWorked
复制代码
如果你尝试这样做,你会在最后一行得到一个错误:
Binary operator '*' cannot be applied to operands of type 'Double' and 'Int'
复制代码
这是因为在Swift中,你不能将*
操作符应用于混合类型。此规则也适用于其他算术运算符。乍一看,这似乎令人惊讶,但Swift是相当有帮助的。
Swift要求您明确表示Int
与Double相乘的含义,因为结果只能是一种类型。您想要结果为Int
型,在执行乘法之前将Double型转换为Int
型吗?或者你想要结果是一个Double型,在执行乘法之前将Int
转换为Double
型?
在本例中,您希望结果为Double
。您不需要Int
,因为在这种情况下,Swift会将hourlyRate常数转换为Int
来执行乘法,四舍五入到19,失去Double
的精度。
你需要告诉Swift,你想让它把工作时间定为双倍,就像这样:
let totalCost: Double = hourlyRate * Double(hoursWorked)
复制代码
现在,当Swift将它们相乘时,每个操作数都将是Double,所以totalCost也是Double。
3: 类型推断
到目前为止,您在本书中看到的每个变量或常量都包含一个类型注释。您可能会问自己,为什么还要写:Int和:Double,因为赋值操作的右侧已经是Int或Double类型。可以肯定的是,这是多余的;你那超聪明的大脑可以毫不费力地看到这一点。
事实证明,Swift编译器也可以推断出这一点。它不需要你一直告诉它类型——它可以自己找出。这是通过一个称为类型推断的过程来完成的。并不是所有的编程语言都有这个功能,但Swift有,这是Swift作为一门语言强大的关键组成部分。
所以,你可以简单地在你看到的大多数地方删除类型。
例如,考虑下面的常量声明:
let typeInferredInt = 42
复制代码
4. Strings
数字在编程中至关重要,但它们并不是你在应用中唯一需要使用的数据类型。Text也是一种非常常见的数据类型,用于表示人名、地址甚至是书中的单词等内容。所有这些都是应用程序可能需要处理的文本示例。
像任何优秀的编程语言一样,Swift可以直接处理字符和字符串。它分别通过数据类型Character和String来实现。在本节中,您将了解这些数据类型以及如何使用它们。
####### 4.1 Characters and strings
字符数据类型可以存储单个字符。例如:
let characterA: Character = "a"
复制代码
它可以存储任何字符,甚至是表情符号:
let characterDog: Character = "?"
复制代码
但是这种数据类型被设计成只能保存单个字符。另一方面,String数据类型存储多个字符。例如:
let stringDog: String = "Dog"
复制代码
这个表达式的右边是一个字符串字面值;它是Swift用于表示字符串的语法。
当然,类型推断也适用于这里。如果你在上面的声明中删除了类型,那么Swift做了正确的事情,使stringDog成为一个String常量:
let stringDog = "Dog" // Inferred to be of type String
复制代码
注意:在Swift中没有字面意义上的字符。字符就是长度为1的字符串。然而,Swift推断任何字符串文字的类型都是string,所以如果你想要一个字符代替,你必须使类型显式。
####### 4.2 字符串拼接
你可以做的远不止创建简单的字符串。有时您需要操作一个字符串,一种常见的方法是将它与另一个字符串组合。
在Swift中,您可以通过一种相当简单的方式做到这一点:使用加法运算符。就像你可以添加数字一样,你也可以添加字符串:
var message = "Hello" + " my name is "
let name = "Matt"
message += name // "Hello my name is Matt
复制代码
你需要将message声明为变量而不是常量,因为你想修改它。可以将字符串字面值相加,如第一行所示,也可以将字符串变量或常量相加,如最后一行所示。
也可以向字符串中添加字符。然而,Swift对类型的严格要求意味着这样做时必须显式,就像处理一个Int型而另一个Double型的数字时必须显式一样。
要将一个字符添加到字符串中,你可以这样做:
let exclamationMark: Character = "!"
message += String(exclamationMark) // "Hello my name is Matt!”
复制代码
使用此代码,您可以在将字符添加到消息之前显式地将其转换为字符串。
####### 4.3 字符串插入
你也可以使用插值来构建一个字符串,这是一种特殊的Swift语法,可以让你以一种易于阅读的方式构建一个字符串:
message = "Hello my name is \(name)!" // "Hello my name is Matt!”
复制代码
我相信你会同意,这比前一节的示例更具可读性。它是字符串字面语法的扩展,您可以用其他值替换字符串的某些部分。将要插入的值括在后跟反斜杠的括号中。
此语法的工作方式与从其他数据类型(如数字)构建字符串的方式相同:
let oneThird = 1.0 / 3.0
let oneThirdLongString = "One third is \(oneThird) as a decimal.
复制代码
这里,你在插值中使用Double。在这段代码的结尾,你的oneThirdLongString常量将包含以下内容:
One third is 0.3333333333333333 as a decimal.
复制代码
当然,将三分之一表示为小数需要无限个字符,因为这是一个循环小数。使用Double的字符串插值使您无法控制结果字符串的精度。这是使用字符串插值的一个不幸结果:它使用起来很简单,但是不能定制输出。
4.4 多行字符串
Swift有一种简洁的方式来表达包含多行字符串。当您需要在代码中放入一个非常长的字符串时,这可能非常有用。
你这样做:
let bigString = """
You can have a string
that contains multiple
lines
by
doing this.
"""
print(bigString)
复制代码
三个双引号表示这是一个多行字符串。方便的是,第一行和最后一行不成为字符串的一部分。这使得它更加灵活,因为您不必将三个双引号与字符串放在同一行上。
在上面的例子中,它将打印以下内容:
You can have a string
that contains multiple
lines
by
doing this.
复制代码
请注意,多行字符串文字中的两个空格的空白被从结果中去掉了。Swift查看最后三双引号行前导空格的数量。用这个作为基线,Swift要求上面的所有行至少有足够的空间,这样它就可以从每一行中删除它。这让您可以用漂亮的缩进来格式化代码,而不会影响输出。
5. 元组
有时数据是成对的或三胞胎的。这方面的一个例子是二维网格上的一对(x, y)坐标。类似地,3D网格上的一组坐标由x值、y值和z值组成。在Swift中,您可以通过使用元组以一种直接的方式表示这些相关数据。
元组是一种类型,它表示由多个任意类型的值组成的数据。您可以在元组中拥有任意多的值。例如,你可以定义一对2D坐标,其中每个轴的值都是一个整数,就像这样:
let coordinates: (Int, Int) = (2, 3)
复制代码
坐标的类型是(Int, Int)。元组内的值类型(在本例中为Int)用逗号分隔,并用圆括号括起来。创建tuple的代码基本相同,每个值用逗号分隔,并用括号括起来。
类型推断也可以推断元组类型:
let coordinates = (2, 3)
复制代码
你可以类似地创建一个双值元组,就像这样:
let coordinatesDoubles = (2.1, 3.5)
// Inferred to be of type (Double, Double)
复制代码
或者你可以混合和匹配组成元组的类型,就像这样:
let coordinatesMixed = (2.1, 3)
// Inferred to be of type (Double, Int)
复制代码
下面是如何访问元组中的数据:
let x1 = coordinates.0
let y1 = coordinates.1
复制代码
你可以通过它在元组中的位置来引用每个项,从0开始。在这个例子中,x1等于2 y1等于3。
在前面的例子中,在索引0处的第一个值是x坐标,在索引1处的第二个值是y坐标,这可能不是很明显。这是另一个例子,说明了为什么总是以避免混淆的方式为变量命名很重要。
幸运的是,Swift允许你命名元组的各个部分,你可以清楚地知道每个部分代表什么。例如:
let coordinatesNamed = (x: 2, y: 3)
// Inferred to be of type (x: Int, y: Int)
复制代码
这里,代码注释了coordinatesNamed的值,以包含元组的每个部分的标签。
然后,当你需要访问元组的每个部分时,你可以通过它的名字访问它:
let x2 = coordinatesNamed.x
let y2 = coordinatesNamed.y
复制代码
这更清楚,更容易理解。通常,为元组的组件命名是有帮助的。
如果你想同时访问元组的多个部分,就像上面的例子一样,你也可以使用一个简写语法来简化:
let coordinates3D = (x: 2, y: 3, z: 1)
let (x3, y3, z3) = coordinates3D
复制代码
这声明了三个新的常量,x3, y3和z3,并将元组的每个部分依次赋值给它们。该代码相当于以下内容:
let coordinates3D = (x: 2, y: 3, z: 1)
let x3 = coordinates3D.x
let y3 = coordinates3D.y
let z3 = coordinates3D.z
复制代码
如果你想忽略元组中的某个元素,你可以用下划线替换声明的相应部分。例如,如果你正在执行一个2D计算,并想要忽略coordinates3D的z坐标,你可以这样写:
let (x4, y4, _) = coordinates3D
复制代码
这行代码只声明了x4和y4。_是特殊的,只是意味着你暂时忽略了这一部分。
五:一大堆数字类型
你一直在用Int表示整数。Int在大多数现代硬件上用64位表示,而在旧的或资源受限的系统上用32位表示。Swift提供了更多使用不同存储容量的数字类型。对于整数,可以使用显式的带符号类型Int8、Int16、Int32、Int64。这些类型分别消耗1、2、4和8个字节的存储。每种类型都使用1位来表示符号。
如果你只处理非负值,有一组显式无符号类型你可以使用。包括UInt8、UInt16、UInt32、UInt64。虽然不能用这些值表示负数,但额外的1位可以表示比有符号的值大两倍的值。
下面是不同整数类型及其字节存储大小的摘要。大多数情况下,你只需要使用Int。
如果你的代码与另一个使用这些更精确大小的软件交互,或者你需要优化存储大小,这些就会变得有用。
你一直在用Double来表示分数。Swift提供了一种Float类型,它的范围和精度都低于Double类型,但需要的存储空间只有Double类型的一半。现代硬件已经针对Double进行了优化,所以它应该是你的首选,除非有很好的理由使用Float。
大多数时候,你只会使用Int和Double来表示数字,但你可能会偶尔遇到其他类型。
例如,假设你需要将一个Int16、一个UInt8和一个Int32加在一起。你可以这样做:
let a: Int16 = 12
let b: UInt8 = 255
let c: Int32 = -100000
let answer = Int(a) + Int(b) + Int(c) // answer is an Int
复制代码
六: Type aliases(类型别名)
Swift的一个有用功能就是能够创建自己的类型,这实际上是另一种类型的别名。这意味着你可以给你的类型一个更有用的名称来描述它是什么,但实际上它下面只是另一个类型。这称为类型别名。
创建类型别名很简单,如下所示:
typealias Animal = String
复制代码
这创造了一种叫做动物的新类型。当编译器看到这个类型时,它只是把它当作一个String。因此你可以这样做:
let myPet: Animal = "Dog"
复制代码
这可能现在看起来不是太有用,但有时类型会变得复杂,为它们创建别名可以给它们一个更简单和更明确的名称。例如,你可以这样做:
typealias Coordinates = (Int, Int)
let xy: Coordinates = (2, 4)
复制代码
这创建了一个名为Coordinates的类型,它是一个包含两个int的元组,然后使用它。
当你看到越来越多的Swift时,你会发现类型别名可以非常强大并简化代码。
七 Protocols
虽然有十几种不同的数字类型,但它们都很容易理解和使用,因为它们基本上都支持相同的操作。换句话说,一旦您知道如何使用Int,使用任何一种风格都是很简单的。
Swift真正伟大的特性之一是,它通过所谓的协议将类型共性的概念形式化。通过学习该协议,您可以立即理解使用该协议的整个类型家族是如何工作的。
在整数的情况下,功能可以这样表示:
箭头表示符合(有时称为采用)协议。虽然此图没有显示整数类型遵循的所有协议—但它让您了解事物是如何组织的。
Swift是第一种基于协议的语言。当您开始理解这些类型背后的协议时,您可以以其他语言无法实现的方式利用该系统。
到本书结束时,你将会接触到现有的协议,甚至创建自己的新协议。