Swift权威指南
上QQ阅读APP看书,第一时间看更新

第2章 千里之行始于足下——Swift语言基础

为了能够使读者更快地上手Swift编程。本章挑选了Swift语言的最基本特性加以介绍。尽管这些特性只占Swift全部特性的很少一部分,但却是所有的Swift程序都必不可少的。所以,读者通过对本章学习,可以使用Swift编写最基本的程序,并对Swift提供的新特性深深地震撼。

本章要点

□ Swift中的分号

□ 变量和常量

□ 常用的数据类型

□ 字符和字符串的常用操作

□ 元组类型

□ 可选类型

□ 注释

2.1 Swift语句和分号

通常来讲,学习一种计算机语言会从Hello World开始。也就是在终端或窗口上输出一行“Hello World”字符串,当然,输出什么都可以,这只是为了练习而已。不过在编写这行代码之前,首先要了解这种编程语言的语句是怎样写的。例如,用Java的语句规则去写Ruby代码,很有可能会出错。

Swift 语言编程格式总体来说中规中矩。不过,关键就在这个分号上。在 Java、C#这样的静态语言中,每条语句必须以分号(;)结尾。也就是说,在这些语言中,每条语句中的分号是可选的。但对于很多动态语言(如Ruby、Python等),如果每一条语句单独占用一行,并不需要加分号,而只有多条语句写在同一行时,才需要加分号。Swift语言吸取了动态语言的这个特性,将分号变成了可选的。例如,下面的 Swift语句都是合法的。

varid = 30

var product_var = "iPad5"; let xyz = 543.12;

id = 123;product_var = "iPhone7"

从这几行代码可以看出,如果在同一行写多条语句,语句之间必须加分号。但多条语句的最后一条语句后面可以不加分号。如果一行只写一条语句,可以不加分号,当然,也可以加分号。不过可能有很多程序员习惯了 C、C++、Java、C#等语言的写法,总习惯在后面加分号。但这也无所谓,反正加一个分号也费不了多少事。

2.2 变量和常量

源代码文件:src/ch02/var_let/var_let/main.swift

没有变量,程序就像一滩死水,没有活力。有了变量,程序才能千变万化,丰富多彩。所以,在编写大量Swift代码之前,先来了解一下Swift是如何定义变量的。在某些情况下,只允许变量设置一次值,然后再也不允许修改了,这种特殊的变量被称为常量。所以,在本节将着重介绍变量和常量。

2.2.1 定义和初始化

不管是什么语言,变量都必须确定数据类型,否则是没法存储数据。但不同的语言,获取数据类型的方式是有区别的。例如,对于静态语言(Java、C#等),必须在定义变量时指定其数据类型。当然,为了让这个变量什么类型的值都能存储,也可以将变量类型设为Object或相似的类型。但不管设为什么,类型是一定要指定一个的。所以,静态语言变量的数据类型是在编译时确定的。

对于动态语言,变量也必须要有一个数据类型,只是这个数据类型并不是在定义变量时指定的,而是在程序运行到为这变量第一次初始化的语句时才确定数据类型。所以,动态语言的变量数据类型是在程序运行时确定的,这也是为什么这种语言被称为动态语言的原因之一。

不过在 Swift 语言中,使用了第三种方式来确定变量的数据类型,这就是“静态定义,动态推导”。其中的“静态定义”就是说变量的数据类型是在编译时确定的。而“动态推导”则说明在定义变量时并不需要明确指定数据类型,而只需使用var定义变量即可,如果要定义常量,需要使用let。然后Swift编译器在编译代码时,会根据变量(常量)初始化时右侧的表达式推导当前变量(常量)的类型。要注意的是,这里的var和let与其他静态语言中的 Object是不一样的。var和 let最终是可以获知准确类型的,而Object只是利用多态技术来存取各种数据类型的值,Object本身是不会反应具体类型的相应特性的(如属性、方法等),除非进行强行转换。例如,下面使用 var 定义了一个id变量。

var id = 20

这行语句是和下面的语句等效的,也就是说,编译器最终会认为20是Int类型,所以,在编译完程序后,会自动将id设为Int类型。所以id拥有Int类型的一切特征。

var id:Int = 20 // Int是数据类型,如何定义数据类型会在本节后面介绍

说道这里,就引出了变量定义的第一个需要注意的地方,就是如果不指定变量的数据类型,该变量必须被初始化。也就是说下面的语句是不合法的。

var id // 不合法,必须初始化

但是,如果为变量指定了一个数据类型,则可以在定义变量时不初始化,例如,下面的语句是合法的。

var id:Int

对于常量来说,不管指定不指定数据类型,都必须进行初始化。例如,下面两条语句都是不合法的。

let const1   // 不合法,常量必须初始化

let const2:Int  // 不合法,常量必须初始化

而要想定义一个常量,必须使用下面的形式。

let const3:Int = 20 // 合法的常量定义(指定数据类型)

let const3 = 20  // 合法的常量定义(不指定数据类型,动态推导)

注意

不管是变量,还是常量。一旦确定了数据值类型,是不能后期改变的。例如,下面的代码会抛出编译错误。

var value = "abc"

value = 20

这里的value已经确定了是字符串(String)类型,不能再次被定义为Int类型。

除此之外还要注意,如果变量或常量在定义时未指定数据类型,初始化什么值都可以。一旦指定了数据类型,必须初始化与数据类型相符的值。例如,下面的代码是错误的。

var value:String = 123 // 必须初始化字符串值

如果想使用一个var或let定义多个变量或常量,可以用逗号(,)分隔多个变量或常量。例如,下面定义的变量和常量都是合法的。

var v1 = "abc", v2:Float = 20.12,v3:Bool

let const1 = "xyz", const2:Double = 12.34;

注意

在大多数静态语言中,指定整数或浮点数,会确认默认的类型。例如,在Java语言中,如果直接写23,会认为23是int类型;如果要让23变成long类型,则需要使用23L。如果直接写23.12,编译器会认为23.12是double类型,如果要将23.12变成float类型,需要使用23.12f。而对于float v = 23.12;,Java编译器会认为是错误的,因为23.12是double,而不是float,正确的写法是float v = 23.12f;。而对于short value = 23;是正确的。因为Java 编译器会自动将23 转换为short 类型,但23必须在short 类型的范围内。这么做是因为在byte code 中byte、short、int 都是使用同一个指令,而float和double使用了不同的指令,所以浮点数没有自动转换(理论上是可以的,估计是某人比较懒,不想麻烦)。在Swift语言中,浮点数会自动转换,所以var v:Float = 20.12是没问题的。

在更深入介绍Swift语言之前,先抛出两个全局函数:print和println。这两个全局函数可以在任何地方使用。用于在终端输出指定的信息(与C语言中的printf函数类似)。其中print输出信息后不换行,要想换行,需要加上“\r\n”,而println函数在输出信息后直接换行。之所以要先介绍这两个函数,是因为在后面的章节会使用这两个函数输出用于演示 Swift 特性的信息。下面是使用这两个函数的简单形式。

var value = 34

let const1:String = "xyz"

println(value)

print(const1 + "\r\n")

在深入使用这两个函数时再详细介绍它们的使用方法。

2.2.2 将变量和常量值插入字符串中

在很多情况下,需要将多个变量的值连接在一起,并插入一些其他的字符串。如果这些变量都是字符串类型,可以直接使用加号(+)连接这些变量值。但如果这些变量不是字符串类型,就需要使用全局函数toString将它们转换为字符串,然后才能使用加号连接。例如,下面代码中定义了1个变量和两个常量,分别为Int、String和Bool类型,现在要将这3个变量和常量的值进行连接,然后使用println函数输出连接的结果。

var productId = 20

let productName:String = "iMac"

let available:Bool = true

println("Product ID:" + toString(productId) + " Product Name:" + productName + " Available:" +toString(available));

执行这段代码后,会输出如下的结果。

Product ID:20 Product Name:iMac Available:true

我们可以看到,尽管通过加号连接可以达到我们的目的,但却使代码变得不易维护和理解。在Swift中提供了一种更好的方法来达到这个目的,就是将变量和常量直接嵌入字符串,这样会使代码看起来更整洁。嵌入变量或常量的格式如下。

\(变量名/常量名)

下面的代码使用这种嵌入变量/常量的方式重新实现了上面的需求。输出结果与前面的代码的输出结果完全相同。

let description = "Product ID:\(productId) Product Name:\(productName) Available:\(available)"

println(description)

注意

经过测试,发现Swift语言的代码在初始化变量或常量时,等号(=)两侧必须有空格,而且在使用加号(+)连接字符串时,两侧必须有空格,否则无法编译通过。例如,var id=12是不合法的,需要使用var id = 12才行。也就是说“id”、“=”和“12”之间要有空格,当然,制表符也可以。有这个现在可能是Swift编译器没有考虑到这种情况,也许还有其他原因。不过添加这个现在是完全不符合逻辑的,大多数语言都没有这个限制。也许当Swift正式版发布时会取消这个限制,大家在编写Swift代码时应注意这一点。

2.2.3 变量和常量的命名规则

由于Swift采用了Unicode 编码,所以几乎可以采用任何自己喜欢的字符作为变量(常量)名。例如,图2-1所示的代码使用了中文和特殊字符(笑脸)作为变量和常量名。

执行图2-1所示的代码后,会输出如图2-2所示的内容。

▲图2-1 使用中文和笑脸作为变量名和常量名

▲图2-2 输出中文和笑脸变量的值

尽管 Swift 中的变量和常量名称可以使用大多数的字符,但仍然有如下几种字符不能包含在变量和常量名称中,或不能作为变量和常量名称。不能使用这些字符的原因主要是为了避免歧义。

□ 数学符号,例如,“+”、“-”、“*”、“/”等运算符号。

□ 箭头。

□ 保留字,如var、let、for、if等。不能作为变量或常量名称,但可以包含在变量和常量名称中。

□ 非法的Unicode字符。

□ 连线和制表符。

如果一定要使用保留字作为变量或常量名的话,需要使用反引号(`)将保留字括起来。例如,下面的代码使用了var作为变量名。

var 'var' = 3

println('var')

尽管使用保留字作为变量名和常量名是可行的,但并不建议这么做。

2.2.4 为变量和常量指定数据类型

尽管 Swift 语言支持类型推导,但如果想为变量或常量指定一个数据类型,也不是不可以的。在前面给出的Swift代码中已经多次这么使用了。

Swift和C风格的语言(Java、C#、C++等)不同,需要在变量或常量的后面指定数据类型,而不是在前面。这一点和Pascal很像。变量(常量)和数据类型之间需要使用冒号(:)分隔,冒号两侧可以有空格(制表符),也可以没有空格。例如,下面是几行典型的为变量和常量指定数据类型的代码。

var id:Int = 20

let productName : String = "Mac mini"

var price: Float = 4688

一旦为变量或常量指定了数据类型,在初始化变量或常量时,就只能指定与数据类型一致的值,Swift编译器不会再进行类型推导。

2.3 数据类型

源代码文件:src/ch02/data_type/data_type/main.swift

Swift语言也和其他语言一样,提供了若干个数据类型可供我们使用。除了几乎任何语言都支持的整数类型、浮点类型、布尔类型、字符串类型等,还指出一些特殊的类型,如元组类型、可选类型。不管是什么数据类型,在 Swift 语言中,数据类型名称都是以大写字母开头,并不存在像Java一样的int、float等内嵌的数据类型。在本节将详细介绍这些数据类型的使用方法。

2.3.1 整数类型

整型是编程语言中最常用的数据类型。在Swift语言中支持8、16、32、64位的整型。这4类整型的类型名称如下。

□ 8位整型:Int8。

□ 16位整型:Int16。

□ 32位整型:Int32。

□ 64位整型:Int64。

在Swift语言中不同的整型拥有不同的类型别名(会在后面详细介绍),这些类型别名如下。

□ Byte:8位整型(UInt8)。

□ SignedByte:8位整型(Int8)。

□ ShortFixed:16位整型(Int16)。

□ Int:32位整型(Int32)。

□ Fixed:32位整型(Int32)。

在实际使用中,可以使用这些类型别名代替相应的整型。例如,下面是使用这些类型和类型别名定义的一些变量和常量。

var value1:Byte = 20

var value2:SignedByte = 30

var value3:ShortFixed = 1234

let value4:Int = 200

let value5:Fixed = 12345

var value6:Int64 = 4433113567

注意

在初始化不同的整型时要注意整型的取值范围。例如,SignedByte 的取值范围是−128 至 127。如果超过了这个取值范围,将无法成功编译程序。例如,var value:Byte = 1234是不合法的。

Swift还提供了另外一套整型,这就是无符号类型。与前面相应的有符号整型对应的无符号整型如下。

□ 8位无符号整型:UInt8。

□ 16位无符号整型:UInt16。

□ 32位无符号整型:UInt32。

□ 64位无符号整型:UInt64。

关于无符号整型应该了解如下几点。

□ 无符号整型除了UInt外,并未定义其他的类型别名。也就是说,并没有像UByte这样的数据类型(至少目前没有)。

□ 无符号整型的最小值是0,不允许设置负数;否则无法成功编译。

每一个有符号整型和无符号整型都有其取值范围,也就是最大值和最小值。例如,Int8的取值范围是−128~127,UInt8的取值范围是0~255。对于取值范围更大的整型,我们也没有必要记。因为每一个整型都有min和max属性,用于获取当前类型的最大值和最小值。例如,下面的代码可以分别获取Int32的最小值和最大值。

let minIntValue = Int32.min;

let maxIntValue = Int32.max;

除此之外,在 Swift 语言中还定义了很多内置的变量,用于获取数值类型的最大值和最小值。例如,INT16_MAX用于获取Int16的最大值,INT16_MIN用于获取Int16的最小值。但有一些数值类型的这些变量并没有定义,估计是因为 Swift 语言目前还是测试版的原因,可能Swift正式版出来后会好一些。

注意

对于Int 类型比较特殊,该类型会随着当前OS X 系统支持的位数不同而不同。例如,如果在32位的OS X系统中,Int = Int32;如果在64位的OS X系统中,Int =Int64。

2.3.2 数制转换

Swift语言中的类型可以表示为十进制、二进制、八进制和十六进制。默认是十进制,数值前面加“0b”为二进制,数值前加“0o”位八进制,数值前加“0x”为十六进制。例如,下面的几个常量分别用十进制、二进制、八进制和十六进制的数值进行了初始化,并输出了这些常量值。

let decimalInt = 20

let binaryInt = 0b1100 //相当于十进制的12

let octalInt = 0o21  // 相当于十进制的17

let hexInt = 0x11   //相当于十进制的17

println(binaryInt);

println(octalInt);

println(hexInt);

这段代码的输出结果如下。

12

17

17

从这一点可以看出,尽管初始化时使用的是二进制表示法,但仍然以十进制数值表示常量值。

2.3.3 浮点类型

常用的浮点数分为单精度和双精度浮点数,数据类型名称分别是Float和Double。其中Double也可以用Float64代替。下面是一些声明为浮点类型的变量和常量。

var value1:Float = 30.12

var value2:Float64 = 12345.54

let value3:Double = 332211.45

2.3.4 数值的可读性

为了增强较大数值的可读性,Swift语言增加了下划线(_)来分隔数值中的数值。例如,如果看到1000000000,估计很少有人会立刻反映出是10亿。大多数人可能需要查0的个数来判断该数值的大小。但使用 1_000_000_000,大多数人(尤其是搞财务的)一眼就可以看出是10亿。

不管是整数,还是浮点数,都可以使用下划线来分割数字。例如,下面是几个使用下划线分隔的数值初始化的变量和常量。

let value1 = 12_000_000

let value2 = 1_000_000.000_000_1

var value3:Int = 1_00_000

要注意的是,下划线两侧的数字不一定是3个为一组。分隔一个或n个数字都可以,例如,1_0_0_0_0_1也是合法的数字,不过这样分隔就没意义了。

2.3.5 数值类型之间的转换

数值转换分为如下几种形式。

□ 整数之间的转换。

□ 浮点数之间的转换。

□ 整数和浮点数之间的转换。

对于整数之间的转换,如果之间使用数值位常量或变量赋值,则会自动转换,当然,前提是数值不超过整型的取值范围。

var value1:Byte = 12

let value2:Int32 = 12

在这两行代码中,同样都是12。但Swift编译器会将第一个12看做是Byte类型的值,而将第二个12看做是Int32类型的值。

如果是一个整型变量(常量)为另一个整型变量(常量)赋值,则需要强行转换。即使将Byte变量值赋给Int变量值也需要强行转换 其实这个可以做得更人性一点,也就是说将一个变量或常量赋给另外一个取值范围更大的变量或常量时,应该自动完成转换。

var int_value1:Int = 100

var byte_value:Byte = Byte(int_value1)

var int_value2:Int = Int(byte_value)

从这几行代码可以看出,强行转换的语法格式如下。

类型名 (变量/常量名)

浮点数之间以及浮点数和整数之间的转换与整数之间的转换类似,同样需要类型强行转换。

var float_value:Float = 1.23

var double_value1:Double = 1.23

var double_value2:Double = Double(float_value)

var int_value:Int = Int(double_value1)

同样是1.23,第一个1.23被认为是Float类型,第二个1.23被认为是Double类型。而将float_value赋给double_value2时仍然需要类型的强行转换。

将浮点数转换为整数时,会采用舍去的方式。例如,1.23和1.99被转换为整数后的结果都是1。

2.3.6 类型别名

Swift中的类型别名和C/C++中的typedef类似,就是为一个类型起一个更有意义的别名。例如,在前面已经介绍过多个类型别名,如Byte、SignedByte等。只是typedef和C/C++中定义变量的规则一样,类型写在前面,自定义的类型名写中后面。代码如下:

typedef int mytype; // C/C++中的类型定义,mytype是新类型

如果在Swift中定义类型别名(新类型),需要用到typealias关键字。typealias的语法格式如下。

typealias 类型别名 = 原始类型

下面的代码就是一个典型的类型别名的例子。其中NewType就是Int32的类型别名。也就是说,在定义变量或常量时,NewType和Int32是完全一样的。

typealias NewType = Int32

var new_value:NewType = 123

2.3.7 布尔类型

布尔类型是Bool,用于进行逻辑判断。例如,if语句就是布尔类型最常用的地方。下面的代码定义了一个布尔类型的变量,然后通过if语句进行判断,如果布尔类型变量值为true,则会执行if语句中的部分,否则会执行else语句中的部分。

var isFile:Bool = true

if isFile

{

println("isFile is true");

}

如果改用布尔类型的地方用了其他数据类型,那么将会编译出错。例如,下面的if语句就无法编译通过。

let i = 20

if i     // 无法编译通过,i不是Bool类型,是Int类型

{

println("i = \(i)");

}

正确的写法应该将 if后面的部分改成Bool类型的值,具体代码如下。

let i = 20

if i == 20   // i = 20的结果是Bool类型,所以可以成功编译

{

println("i = \(i)");

}

2.4 字符和字符串

源代码文件:src/ch02/char_string/char_string/main.swift

字符和字符串也是 Swift 语言中的两个重要类型,只是由于这两个数据类型较复杂,所以单独用一节详细讨论。

2.4.1 字符类型的常量和变量

字符类型的值只能存储一个字符(Unicode字符),使用Character表示字符类型。Swift语言中的字符类型与大多数语言中的字符表示法不同。例如,在Java中,字符需要使用单引号(')括起来,而在Swift语言中,字符和字符串都要使用双引号(")括起来。

let charValue1:Character = "a"

var charValue2:Character = "上"

使用字符类型时应了解如下两点。

□ 由于字符和字符串都使用双引号,所以,要想将变量或常量定义为字符类型,必须指定Character。如果不指定任何类型,并且直接使用值进行初始化。Swift编译器会认为用双引号括起来的是字符串,而不是字符。当然,如果右侧是一个返回 Character 的函数或方法,仍然会认为左侧的变量或常量是 Character 类型的值。

□ 虽然字符和字符串都使用双引号。但如果将变量或常量声明为 Character,双引号括起来的部分只能是一个字符,0个字符和大于1个字符都不可以,否则Swift编译器会报错。

2.4.2 字符串类型的常量和变量

字符串类型是 String。如果在定义变量或常量时,不指定类型。系统会认为用双引号括起来的部分是字符串。下面是几个String类型的常量和变量的定义及初始化代码。

var strValue1:String = "abcd"

let strValue2 : String = "变形金刚5"

如果要初始化一个空串(长度为 0 的字符串),可以直接使用(""),也可以使用 String对象(构造方法没有参数值)。如果想为字符串设置值,也可以用String对象,只需要在String的构造方法中传入要初始化的字符串即可。

var emptyString1 = ""   // 初始化一个空的字符串变量

var emptyString2 = String(); // 初始化一个空的字符串变量

var str1 = "abc"

var str2 = String("abc")

2.4.3 枚举字符串中的所有字符

字符串是由字符组成的,也就是说,字符串是字符的集合。那么也就可以用枚举集合中元素的方法来枚举字符串中的字符。所以,可以使用下面的for循环来枚举charCollection中的所有字符。

let charCollection = "获取字符串中的每一个字符"

for c in charCollection

{

print("<" + c + ">")

}

执行这段代码后,会输出如下的内容。

<获><取><字><符><串><中><的><每><一><个><字><符>

2.4.4 获取字符串中字符的Unicode编码

由于在 Swift语言中每一个字符都使用了Unicode编码,所以,也可以获取字符串中每一个字符的Unicode编码。

String类有一个unicodeScalars属性,用于获取字符串中字符的Unicode集合,可以使用下面的代码获取一个字符串中的所有字符的Unicode。

let strValue3 = "abcd上下左右"

for c in strValue3.unicodeScalars

{

print("\(c.value) ")

}

执行这段代码后,会输出如下的内容。

97 98 99 100 19978 19979 24038 21491

从输出结果可以看出,前4个字符由于是标准的ASCII,所以输出的Unicode值都小于255,而后面4个字符是汉字(占用双字节),所以输出的Unicode值都很大。

2.4.5 字符串和字符的连接

不仅字符串和字符串之间可以使用加号(+)进行连接,字符串和字符之间也可以使用。例如,下面的代码连接了一个字符和一个字符串,并输出了连接结果。

let strValue4 = "abcd"

let charValue3:Character = "e"

let strValue5 = strValue4 + charValue3

println(strValue5);

输出结果如下:

abcde

2.4.6 在字符串中包含特殊字符

有时需要在字符串中包含特殊字符。这些特殊字符主要分如下两种情况。

□ 会引起歧义的字符,如双引号、单引号。

□ 不好输入的字符,例如,一个心形字符。

对于第一种情况,直接使用转义符即可。例如,下面的代码在字符串中包含了单引号和双引号,这两个特殊符号使用反斜杠(\)进行转义。

let strValue6 = "\"今天好热,不想出去,不想上班,只想\'睡觉\'\""

println(strValue6);

执行这段代码后,输出的结果如下。

“今天好热,不想出去,不想上班,只想'睡觉'”

下面是比较常用的使用转义符输出的字符。

□ \0:null字符。

□ \\:反斜杠。

□ \t:制表符。

□ \n:新行符号。

□ \r:回车符。

□ \":双引号。

□ \':单引号。

如果要在字符串中包含第二类特殊字符,需要使用十六进制表示的Unicode编码。该编码分为1个字节、两个字节和4个字节表示法。分别用“\xnn”、“\unnnn”和“\Uxnnnnnnnn”表示。其中n表示十六进制。下面的代码分别用这3种Unicode表示法输出不同的特殊字符。

let strValue7 = "\x41"   // A

println(strValue7)

let strValue8 = "\u2600"  // ☀

println(strValue8)

// 4字节Unicode字符,使用\Unnnnnnnn

let strValue9 = "\U0001F496" //心形字符

println(strValue9)

2.4.7 字符串之间的比较

Swift中的字符串之间的比较很简单。不管参与比较的双方是值,还是变量,只需直接使用“==”,“<”和“>”比较即可。

var strValueA = "abcd"

var strValueB = "abce"

if strValueA > strValueB

{

println("strValueA > strValueB");

}

else if strValueA < strValueB

{

println("strValueA < strValueB");

}

else

{

println("strValueA = strValueB");

}

2.4.8 字符串的大小写转换

使用String的两个属性(uppercaseString和lowecaseString),可以将字符串中的26个英文字母从小写转换为大写,或从大写转换为小写。

let lowercaseStr = "abcdefg"

// 从小写转换为大写

let uppercaseStr = lowercaseStr.uppercaseString

println(uppercaseStr);

// 从大写转换为小写

println(uppercaseStr.lowercaseString)

2.5 元组(tuples)类型

源代码文件:src/ch02/tuples/tuples/main.swift

元组类型是Swift语言提供的一种新数据类型,英文名字是tuples。尽管元组类型是新的,但其表现形式和其他类型(如结构体,数组)还有一些类似。元组,说白了,就是可以包含多个值的数据类型。

2.5.1 元组类型的定义

元组类型没有像那些简单类型(如 String、Int)一样有类型名,不过如果初始化时指定了元组值,Swift编译器会自动推导出该变量或常量是元组类型。元组类型变量(常量)在初始化时使用一对圆括号将元组中的值括起来。例如,下面的代码定义了一个元组类型常量,并输出了这个常量值。

let product1 = (20, "iPhone6", 5888)

println(product1)

当执行这两行代码时,会输出如下的结果。

(20, iPhone6, 5888)

很明显,product1表示的元组类型常量包含了3个值,其中20是Int类型的值,“iPhone6”是String类型的值,5888是Int类型的值。

2.5.2 获取元组中的元素值

既然元组类型可以存储多个值,那么就面临一个问题,如何获取元组中某个元素的值呢?其实很简单,只需要将元组类型中每一个元素的值分别赋给不同的变量或常量即可。赋值的方法是用圆括号定义多个变量或常量,然后使用元组类型值在右侧赋值,具体代码如下:

let product1 = (20, "iPhone6", 5888);

var (id, name, price) = product1;// 分别将product1中的3个值赋给3个变量(id,name,price)

// 分别输出product1中的3个值

println("id=\(id) name=\(name) price=\(price)");

如果我们只想获取元组类型值中的一个或几个值,并不想全部获取这些值,那么对那些不打算获取值的元组元素,可以使用下划线(_)占位。例如,下面的代码只获取了product1的第二个元素值,也就是name,其他的位置都使用下划线替代(不需要定义变量或常量)。

let (_,name1,_) = product1

println("name1=\(name1)")

2.5.3 为元组中的元素命名

其实,从元组类型值中获取其元素的值还有一个更简单的方法,就是为每一个元素命名,引用的方法和引用对象属性相同。例如,下面的代码为product3中的3个元素命名,并且输出了其中的name元素值。

let product3 = (30, name:"iPhone8", price:5988)

println(product3.name)

如果嫌麻烦,可以只为需要单独获取值的元素命名,那些不需要通过直接引用方式获取值的元素可以不为其命名。

2.6 可选类型

可选类型也是 Swift 语言新添加的对象。主要是为了解决对象变量或常量为空的情况。在前面定义的变量和常量都不能为空。里面必须要有值。

Swift中的可选类型则允许变量(常量)中没有值(被设为nil)。要注意的是,Swift中的nil和Objective-C中的nil不一样。前者的nil表示没有值,而后者的nil表示变量值为空。

可选类型需要在类型后面加一个问号(?)。一个典型的例子是String类有一个toInt方法,该方法会将字符串转换为 Int 类型。不过这里就有一个问题,字符串中可能包含非数字的字符,这时就会转换失败。如果在其他语言中,不添加错误捕捉,可能会抛出异常,而在Swift中,如果转换失败,就直接返回nil。表示没有返回Int类型的值(反正你想加捕捉异常做不到,因为Swift中压根就没有异常捕捉)。这里的toInt方法返回的就是一个可选类型。

下面的代码将numStr中的值转换为Int类型的值,并输出该值。

var numStr:String = "123"

var value:Int? = numStr.toInt(); // value必须定义为可选的Int类型,否则无法成功编译

println(value)

如果将123改成会导致转换失败的值,如a123,那么执行上面的代码后就会输出nil了。

对于一个可选类型的变量或常量,通常在使用之前需要进行判断,例如,下面的代码会对value进行判断,如果确实转换成功(不为nil),则输出一行文本。

if value != nil

{

println("转换成功")

}

不过每次使用value变量时都进行判断很麻烦,所以,Swift添加了一个用于确定可选类型变量一定有值的感叹号(!)。当对value第一次判断后,如果能确认该变量已经被赋了值,后面就可以直接在value后加感叹号使用该变量了。

println(value!)

如果在可选类型变量(常量)后面加感叹号,当变量(常量)为 nil 时,程序会中断,并在终端输出如下的信息。

fatal error: unexpectedly found nil while unwrapping an Optional value

所以在可选变量(常量)后面加感叹号时,要确保该变量(常量)中一定有值。

现在已经知道了,在引用可选类型变量(常量)时,后面加感叹号是为了在该变量(常量)中没有值(为nil)时抛出运行时异常。那么每次都加感叹号也挺麻烦,所以干脆在定义可选类型变量(常量)时将问号改成感叹号,这样就可以直接引用这些可选类型变量和常量了。

var numStr:String = "123"

var value:Int! = numStr.toInt(); // value必须定义为可选的Int类型,否则无法成功编译

// 即使value后面不加!(当然,加!也没问题),当value为nil时以后抛出异常

println(value)

注意

在使用可选类型变量(常量)时,如果使用问号(?)来定义,并且单独使用这些变量和常量,后面是可以不加感叹号(!)的。但要让这些变量和常量和其他的值通过操作符进行操作,那就必须要加感叹号了。如下面的代码是合法的。

var numStr:String = "123"

var value:Int! = numStr.toInt();

println(value) // 如果value为nil,则会输出nil

但下面的代码就无法成功编译了。

var numStr:String = "123"

var value:Int! = numStr.toInt();

println(value + 4)// 编译失败,value必须写成value!,或在定义value时使用“!”

2.7 注释

Swift和C++的注释几乎是相同的,也支持单行注释(用“//”进行注释)和多行注释(用“/* ... */进行注释)。不过Swift对其做了扩展。在多行注释中可以嵌套多行注释。例如,下面的注释在Swift语言中是合法的。

/* 上层的多行注释

/* 嵌套的

多行注释

*/

*/

2.8 小结

本章所介绍的关于Swift的知识是Swift语言最基础的部分。希望读者认真阅读本章的内容。因为在后面的章节将会大量涉及本章讲的内容,如果读者对这些内容不了解,阅读Swift源代码会有很大困难。