第4章 Swift中的加减乘除——运算符和表达式
本章教学录像:46分钟
计算机的最基本功能就是计算,所以任何一门程序设计及编程语言的计算能力都是非常重要的。本章主要学习Swift编程语言的计算方法。其中包括基本运算符、高级运算符、自定义运算符以及如何进行自定义类型的运算符重载等。
本章要点(已掌握的在方框中打勾)
□ 运算符
□ 赋值运算符
□ 数值运算
□ 高级运算符
□ 表达式
4.1 Swift中的运算符
本节视频教学录像:4分钟
在Swift语言中,程序要对数据进行大量的运算,就必须利用运算符操纵数据。用来表示各种不同运算的符号称为运算符,由运算符和运算分量(操作数)组成的式子称为表达式。
4.1.1 运算符的概念
运算符是检查、改变、合并值的特殊符号或短语的统称。例如,加号(+)是计算两个数的和(如let i=1+2),复杂些的运算符包括逻辑与(&&,如if enteredDoorCode && passedRetinaScan),还有自增运算符(++i)这样让自身加一的便捷运算。
Swift支持大部分标准C语言的运算符,且改进许多特性来减少常规编码错误。如赋值符=不返回值,以防错把等号==写成赋值号=而导致Bug出现。数值运算符(+、-、*、/、%等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围而导致的异常结果。当然也可以选择使用Swift的溢出运算符进行溢出运算,具体使用方法可参见4.7.2小节溢出运算符。
区别于C语言,在Swift中可以对浮点数进行取余运算(%)。除此之外,Swift还提供了C语言没有的表达两数之间的值的区间运算符(a..b 和 a...b),这样可方便我们使用一个区间内的数值。
4.1.2 运算符的级别
按运算符在表达式中与运算分量的关系(连接运算分量的个数),运算符可分为如下3种。
⑴一目运算符,即一元运算符,一目运算符对单一操作对象操作,如-5,!a。
一目运算符分前置运算符和后置运算符,前置运算符需紧排操作对象之前,如!b;后置运算符需紧跟操作对象之后,如 i++。
⑵二目运算符,即二元运算符,需要两个运算分量,如a+b,x||y。
双目运算符操作两个操作对象,如2+3是中置的,因为它们出现在两个操作对象之间。
⑶三目运算符,即三元运算符,需要3个运算分量,如a>b?a:b。
三目运算符操作三个操作对象,和C语言一样,Swift只有一个三目运算符,就是三目条件运算符a ? b : c 。受运算符影响的值叫操作数,在表达式1 + 2中,加号+是双目运算符,它的两个操作数是值1和2。
提示
所有的双目运算符都可以与赋值运算符一起构成复合赋值运算符。
4.2 赋值运算符
本节视频教学录像:24分钟
赋值运算符是用来给变量赋值的。它是双目运算符,用于将一个表达式的值送给一个变量。
4.2.1 赋值运算符
赋值运算符有一个基本的运算符(=)。
在Swift语言中允许在赋值运算符“=”的前面加上一些其他的运算符,以构成复合的赋值运算符。复合赋值运算符共有10种,分别为:+=、-=、*=、/=、%=、<<=、>>=、&=、^=、!=。
【范例4-1】单元组赋值。
01 letb= 10 //为常量b赋值为10
02 var a= 5 //为变量a赋值为5
03 a=b //将b的值又赋给a,那a现在就等于10
范例分析:本例通过赋值运算符“=”来运算a = b,完成用常量b的值来初始化或更新变量a的值。
【范例4-2】多元组赋值。
01 let (x, y)= (1, 2) //通过多元组为多元常量赋值
02 //现在 x等于 1, y等于 2
范例分析:如果赋值的右边是一个多元组 ,它的元素可以马上被分解成多个常量或变量来赋值。
另外,Swift与C语言和Objective-C不同,其赋值操作并不返回任何值,所以以下代码是错误的。
01 if x= y { //此句错误 ,因为 x= y并不返回任何值
02 }
这个特性能避免把==错写成=,由于 if x= y是错误代码 ,Swift从底层帮你避免了这些代码错误。
1.数值运算
Swift让所有数值类型都支持了(加法+、减法-、乘法 *和除法 /)的四则基本运算。
【范例4-3】四则基本运算。
01 1+ 2 //等于 3
02 5 - 3 //等于 2
03 2 * 3 //等于 6
04 10.0 / 2.5 //等于 4.0
Swift与C语言和Objective-C不同的是,Swift默认不允许在数值运算中出现溢出情况。但你可以使用Swift的溢出运算符来达到你有目的的溢出(如 a &+ b),具体内容将在高级运算符中的溢出运算符小节讲解。
【范例4-4】字符串加法运算。
01 "hello, "+ "world"
02 //等于 "hello,world"
通过上例我们可以发现在Swift数值运算中,可以使用加法操作“+”来完成字符串的拼接与组合操作。
【范例4-5】字符串赋值运算。
字符串赋值运算也用于字符串的拼接。
01 letdog:Character= "d" //常量dog字符的值为“d”
02 let cow:Character= "c" //常量cow字符的值为“c”
03 letdogCow=dog+ cow //常量dogCow字符现在是“dc”
两个字符类型或一个字符类型和一个字符串类型,相加会生成一个新的字符串类型。
2.求余运算
求余运算符是一个百分号“%”。注意这个%并不是除号÷,它是一个取余运算符,也叫作模运算符。取余的意思是,取得两个整数相除之后的余数。比如5除以2的余数是1,5除以3的余数是2。
例如:求5除以2的余数。
a= 5% 2 //求5除以2的余数a的值,在此a的值为1
【范例4-6】正数求余。
01 a= 5% 2 //求5除以2的余数a的值,在此a的值为1
02 b= 2% 5 //求2除以5的余数b的值,在此b的值为2
简单计算可得:a的值为1,b的值为2。
【范例4-7】负数求余。
01 a=-5% 2 //求 -5除以2的余数a的值,在此a的值为 -1
02 b= 5% -2 //求5除以2的余数b的值,在此b的值为1
02 c= -5% -2 //求 -5除以2的余数c的值,在此c的值为 -1
利用%求出来的余数是正数还是负数,由%左边的被除数决定,被除数是正数,余数就是正数,反之则反。因此,a、b、c的值分别是-1、1、-1。
提示
在对负数 -b求余时,-b的符号会被忽略,这意味着a%b和a% -b的结果是相同的。
3.浮点数求余
Swift不同于C语言和Objective-C,在Swift中是可以对浮点数进行求余的。
【范例4-8】浮点数进行求余。
01 8%2.5
02 //求8除以2.5的余数,值为0.5
在这个例子中,8除以2.5等于3余0.5,所以结果是0.5。
4.自增和自增运算
和C语言一样,Swift也提供了方便对变量本身加1或减1的自增++和自减--的运算符,其操作对象可以是整型和浮点型。
01 var i= 0 //为变量 i赋值为0
02 ++ i //现在 i=1
每调用一次++i, i的值就会加 1。实际上,++i 是 i = i + 1 的简写,,而--i 是 i = i - 1 的简写。++和--既是前置又是后置运算。++i, i++, --i 和 i--都是有效的写法。
(1)++是自增运算符。如a++、++a,都等价于a = a+1。
(2)--是自减运算符。如a--、--a,都等价于a = a-1。
提示
如果写5++就是错误的,因为5是常量。
【范例4-9】++a和a++的区别。
方式一:
01 a= 10
02 a++
方式二:
01 a= 10
02 ++a
单独使用++a和a++时,它们是没有区别的。方式一和方式二两段代码的效果都是让a的值+1,最后a的值都为11。
方式三:
01 var a= 10
02 var b=++a
方式四:
01 var a= 10
02 varb= a++
方式三和方式四两段代码的执行结果是有区别的。
方式三代码:++a的意思是先对a执行+1操作,再将a的值赋值给b。因此,最后a、b的值都是11。
方式四代码:a++的意思是先将a的值复制出来一份,然后对a执行+1操作,于是a变成了11,但是复制出来的值还是10,a++运算完毕后,再将复制出来的值10赋值给了b,所以最后变量b的值是10,变量a的值是11。
提示
同理,--a和a--的区别也是一样的。
5.单目负号和单目正号
单目负号和单目正号也称为单目减加。单目减运算符相当于取相反值,若是正值就变为负值,若是负值就变为正值。单目加运算符没有意义,纯粹是和单目减构成一对用的。
数值的正负号可以使用前缀-(即单目负号)来切换。
【范例4-10】单目负号。
01 let three= 3
02 letminusThree= -three //minusThree等于 -3
03 letplusThree= -minusThree //plusThree等于 3,或 "负负 3"
在这个例子中,单目负号遵循负负得正的原则,写在操作数之前,中间没有空格。
【范例4-11】单目正号。
01 letminusSix= -6
02 let alsoMinusSix=+minusSix //alsoMinusSix等于-6,前面的+符号不会影响minusSix的值
单目正号+对所返回的操作数值不会做任何改变。虽然单目+做无用功, 但当你在使用单目负号来表达负数时,你可以使用单目正号来表达正数,如此你的代码才会具有对称美。
4.2.2 复合赋值
Swift 也提供把其他运算符和赋值运算=相组合的复合赋值方式,也称作复合赋值运算符。
【范例4-12】加赋运算 +=。
01 var a=5
02 a+=3 //表达式a+= 3等同于a= a+ 3,a的初始值是5那么现在a是8
表达式a += 3 是 a = a + 3 的简写,一个加赋运算就把加法和赋值两件事完成了。
提示
复合赋值运算没有返回值,let b = a += 2 这类代码是错误。这不同于上面提到的自增和自减运算符。
表达式中其他复合赋值运算符的方式如下。
⑴ += 加赋值运算符。如a+=3+2,等价于a=a+(3+2)。
⑵ -= 减赋值运算符。如a-=3+2,等价于a=a-(3+2)。
⑶ *= 乘赋值运算符。如a *=3+2,等价于a=a *(3+2)。
⑷ /= 除赋值运算符。如a /=3+2,等价于a=a /(3+2)。
⑸%= 取余赋值运算符。如a %= 3+2,等价于 a = a %(3+2)。
4.3 比较运算符
本节视频教学录像:9分钟
所有标准 C语言中的比较运算都可以在Swift中使用。比较运算符中的“比较”二字指的是两个运算数值分量间的大小关系,与数学意义上的比较概念相同,只不过比较运算符的表示方式有所不同。
等于 a == b
不等于 a != b
大于 a > b
小于 a < b
大于等于 a >= b
小于等于 a <= b
提示
Swift也提供恒等===和不恒等!==,这两个比较符用来判断两个对象是否引用同一个对象实例,更多细节在类与结构小节中介绍。
每个比较运算都返回了一个标识表达式是否成立的布尔值。
⑴1==1 // true,因为1等于1
⑵2 !=1 // true,因为2不等于1
⑶2>1 // true,因为2大于1
⑷1<2 // true,因为1小于2
⑸1>=1 // true,因为1大于等于1
⑹2<=1 // false,因为2并不小于等于1
比较运算多和条件语句配合使用,如 if 条件语句。
【范例4-13】比较运算配合条件语句。
01 let name= "liming"
02 if name== "liming" {
03 println("hello, liming") //如果名称等于 liming,输出“hello,liming”
04 } else {
05 println("对不起 , \(name),我不认识你 !") //如果不是则输出“对不起,某某,我不认识你!”
06 }
本例是一个带有条件的比较运算,最终输出“hello, liming”,因为“name”常量被赋的值就是等于"liming"。
4.3.1 比较运算符的书写规则
Swift中共包含6种比较运算符,分别是:>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于),它们都是双目运算符。以下这些运算符不同于平时的书写习惯。
⑴“>=”和“<=”不能写成“≥”和“≤”。
⑵等于在C语言中的书写格式为“==”而不是“=”,注意是两个“=”连起来写表示相等的关系,“=”是后面要讲的赋值运算符,应注意二者的区别。
⑶不等于的书写格式为“!=”,而不是平时书写的“≠”。
4.3.2 比较表达式
用比较运算符把两个表达式连接起来的式子称为比较表达式,亦称关系表达式。关系表达式的结果只有两个:1和0。关系表达式成立时值为1,不成立时值为0。
例如,若x=3,y=5,z=-2,则:
x+y<z 的结果不成立,表达式的值为0。
x!=(y>z) 的结果成立,表达式的值为1(因为y>z的结果成立,值为1,x不等于1结果成立,整个表达式的值为1)。
4.3.3 比较运算符的优先级和结合性
在这6种比较运算符中,“>”“>=”“<”和“<=”的优先级相同,“==”和“!=”的优先级相同,前4种的优先级高于后两种。
例如:
a==b<c 等价于 a==(b<c)
a>b>c 等价于 (a>b)>c
比较运算符中的结合性均为“左结合”。
数值型数据比较的是数值的大小,而字符型数据比较的是ASCII码的大小。
例如,“a”==“A”的值为0,因为“a”的ASCII码值为97,“A”的 ASCII码值为65,二者不相等,结果不成立。
4.3.4 比较运算范例
本小节通过一个范例来学习比较运算符和表达式的使用方法。
【范例4-14】输出程序中表达式的值。
01 var a:Bool,b:Int,c:Int
02 c=10
03 b=c //b,c均赋值为10
04 a=b==c //将b==c的结果赋值变量a
05 println(" a=\(a),b=\(b),c=\(c)") //分别输出a,b,c的值
06 a=b>c&&c>=100 //将b>c&&c>=100的结果赋给变量a
07 println(" a=\(a),b=\(b),c=\(c)") //分别输出a,b,c的值
本范例重点考察了逻辑运算符的使用及表达式的值。如a=b==c是先计算b==c的值,由于逻辑表达式的值只有0和1,也就是布尔类型的false和true,b与c相等,则b==c的值为true,然后再将true赋给变量a,通过println语句输出3个变量的值,观察是否有变量。
4.4 三目条件运算符
本节视频教学录像:2分钟
条件运算符由“?”和“:”组成,是Swift语言中唯一的一个三目运算符,是一种功能很强的运算符。用条件运算符将运算分别连接起来的式子称为条件表达式。
三目条件运算符的特殊在于它是有三个操作数的运算符,它的原型是问题 ? 答案1 :答案2。条件表达式的一般构成形式如下。
问题?答案1:答案2
它简洁地表达根据“问题”成立与否做出在两个答案中二选一的操作。如果“问题”成立,返回“答案 1”的结果,否则则返回“答案 2 ”的结果。
使用三目条件运算简化了以下代码。
01 ifquestion: { //判断问题是否成立
02 answer1 //如果问题成立则回答1
03 }
04 else { //如果问题不成立则回答2
05 answer2
06 }
另外,三目条件运算符不限于简单的算术表达式,甚至可以是函数的调用。
例如:y>x?println("OK!"):println("NO!");//如果y>x,输出“OK!”,否则输出“NO!”
条件运算符的结合性是“右结合”,它的优先级别低于算术运算符、关系运算符和逻辑运算符。
例如:a>b?a:c>d?c:d,等价于:a>b?a:(c>d?c:d)。
【范例4-15】三目条件运算符和表达式的应用。
方式一(三目条件运算方式):
01 let contentHeight= 40
02 let hasHeader= true
03 let rowHeight= contentHeight+ (hasHeader ? 50:20) // rowHeight现在是 90
这里有个计算表格行高的例子。如果有表头,那么行高应比内容高度要高出 50 像素;如果没有表头,只需高出 20 像素。
方式二(普通条件运算方式):
01 let contentHeight= 40
02 let hasHeader= true
03 var rowHeight= contentHeight
04 if hasHeader {
05 rowHeight= rowHeight+ 50
06 } else {
07 rowHeight= rowHeight+ 20
08 }
09 // rowHeight现在是 90
通过比较可知,方式一要比方式二的代码简洁很多。方式一的代码例子使用了三目条件运算,所以一行代码就能解决问题。这比方式二的代码简洁得多,无需将 rowHeight 定义成变量,因为它的值无需在 if 语句中改变。
三目条件运算符提供了有效率且便捷的方式来表达二选一的选择。需要注意的是,过度使用三目条件运算符会将简洁的代码变成难懂的代码。在工作中应避免在一个组合语句中使用多个三目条件运算符。
4.5 区间运算符
本节视频教学录像:7分钟
Swift编程语言提供了“闭区间运算符”和“半闭区间运算符”两个区间运算符来表达一个区间值的运算方法。
4.5.1 闭区间运算符
闭区间运算符用于定义一个区间的所有值,如a...b 定义一个包含从 a 到 b(其中包括 a 和 b)区间的所有值。闭区间运算符在辗转使用一个区间所有值的时候非常有用。闭区间的构成形式如下。
for-in a...b 指循环使用a到b的值且包含a和b。
【范例4-16】 1~5数值与5的乘积运算。
01 for index in 1...5 { //声明一个1~5的数值区间
02 println("\(index) * 5= \(index * 5)") //循环输出声明区间数值乘5的算式、等式以及乘积
02 }
此程序的运行过程是依次从1~5的数值区间取出数值和5相乘,并输出运算结果。
4.5.2 半闭区间运算符
半闭区间a..<b用于定义一个从a到b但不包括b的区间。之所以称为半闭区间,是因为该区间包含第一个值而不包括最后的值。
半闭区间构成形式如下。
for-in a..<b 指循环使用a到b的值且包含a但不包含b。
半闭区间的实用性在于当你使用一个从0开始的列表(如数组)时,能够非常方便地从0数到列表的长度。
【范例4-17】输出数组元素。
01 let names= ["a", "b", "c", "d"]
02 let count= names.count
03 for i in 0..<count { // 0~4的半闭区间数组,包含0不包含4共计4个元素
04 println("第 \(i+ 1)个字母是 \(names[i])")
05 }
程序运行过程是依次从0~4的数值半闭区间计数,并输出运算结果。
提示
数组有 4 个元素,但 0..<count 只数到3(最后一个元素的下标),因为它是半闭区间。
4.6 逻辑运算符和表达式
本节视频教学录像:9分钟
什么是逻辑运算?逻辑运算是用来判断一件事情是“成立”还是“不成立”或者是“真”还是“假”的,判断的结果只有两个值,用数字表示就是“1”和“0”。其中“1”表示该逻辑运算的结果是“成立”的,“0”表示这个逻辑运算式表达的结果“不成立”。这两个值称为“逻辑值”。
假如一个房间有两个门,A门和B门。要进房间从A门进可以,从B门进也行。用一句话来说是“要进房间去,可以从A门进‘或者’从B门进”,用逻辑符号来表示这一个过程如下。
能否进房间用符号C表示,C的值为1表示可以进房间,为0表示进不了房间;A和B的值为1时表示门是开的,为0表示门是关着的。那么:
两个房间的门都关着(A、B均为0),进不去房间(C为0);
B是开着的(A为0、B为1),可以进去(C为1);
A是开着的(A为1、B为0),可以进去(C为1);
A和B都是开着的(A、B均为1),可以进去(C为1)。
4.6.1 逻辑运算符
逻辑运算符主要用于逻辑运算,包含“!”(逻辑非)、“&&”(逻辑与)、“||”(逻辑或)等3种。逻辑运算符的真值表如下。
其中,“!”是单目运算符,而“&&”和“||”是双目运算符。
逻辑非(!),其结果是运算分量逻辑值的“反”。
逻辑与(&&),仅当两个运算分量同时为真时,结果才为真;否则,只要其中有一个为假,结果就为假。
逻辑或(||),只要其中有一个运算分量为真,结果就为真;仅当二者同时为假时,结果才为假。
1.逻辑非
逻辑非运算!a是对一个布尔值取反,使得true变false或false变true。它是一个前置运算符,需出现在操作数之前,且不需加空格,读作非a,范例如下。
01 let allowedEntry= false
02 if !allowedEntry { //只有在 "非allowentry"为 true,即allowEntry为 false时被执行
03 println("ACCESSDENIED") //执行时输出 "ACCESSDENIED"
04 }
在这里“if !allowedEntry”语句可以读作“如果非 alowed entry”,接下一行代码只有在如果“非allow entry”为 true,即allowEntry为false 时才被执行。
在示例代码中,小心地选择布尔常量或变量有助于代码的可读性,并且可避免使用双重逻辑非运算或混乱的逻辑语句。
2.逻辑与
逻辑与a && b表达了只有a和b的值都为true时,整个表达式的值才会是true。只要任意一个值为false,整个表达式的值就为false。
以下例子,只有两个值都为true真值的时候才允许进入。
01 let enteredDoorCode= true
02 letpassedRetinaScan= false
03 if enteredDoorCode&&passedRetinaScan { //逻辑与运算
04 println("Welcome!") //当逻辑与结果为 true时允许进入
05 } else {
06 println("ACCESSDENIED") //执行时输出 "ACCESSDENIED"
07 }
事实上,如果第一个值为false,那么就不需要在去计算第二个值了,因为第二个值已经不能影响表达式的结果了。此种设计这被称作“短路计算”。
3.逻辑或
逻辑或a||b是一个由两个连续的|组成的中置运算符。它表示了两个逻辑表达式的其中一个为true,整个表达式就为 true。同逻辑与运算类似,逻辑或也是“短路计算”的,当左端的表达式为true时,就不计算右边的表达式了,直接反馈给true,因为它不可能改变整个表达式的值了。
01 let hasDoorKey= false
02 let knowsOverridePassword= true
03 if hasDoorKey || knowsOverridePassword { //逻辑或运算
04 println("Welcome!") //当逻辑与结果为 true时允许进入
05 } else {
06 println("ACCESSDENIED") //执行时输出 "Welcome!"
07 }
在此例中,第一个布尔值hasDoorKey为false,但第二个值knowsOverridePassword为true,所以整个表达式的结果是true,于是门被允许进入,输出"Welcome!"。
4.组合逻辑
在逻辑运算中允许组合多个逻辑运算来表达一个复合逻辑,范例如下。
01 if enteredDoorCode&&passedRetinaScan || hasDoorKey || knowsOverridePassword {
02 println("Welcome!") //执行时输出 "Welcome!"
03 } else {
04 println("ACCESSDENIED")
05 }
这个例子使用了包含多个&&和||的复合逻辑。但无论怎样,&&和||始终只能操作两个值。所以这是三个简单逻辑连续操作的结果,分析如下。
如果能输入正确的密码同时通过视网膜扫描,或者我们有一把有效的钥匙,又或者知道紧急情况下重置的密码,就可以把门打开进入房间。前两种情况都不能满足,所以前两个简单逻辑的结果是false假,但是我们是知道紧急情况下重置的密码的,那么现在这个复杂的表达式值便是true,最终输出为"Welcome!"。
4.6.2 逻辑表达式
逻辑运算符把各个表达式连接起来组成一个逻辑表达式,如a && b、1||(!x)。逻辑表达式的值也只有两个:0和1。0代表结果为假,1代表结果为真。
例如,当x为0时,x<-2 && x>=5的值为多少。
当x=0时,0<-2结果为假,值等于0;0>=5结果也为假,值为0;0 && 0,结果仍为0;
当对一个量(可以是单一的一个常量或变量)进行判断时,C编译系统认为:0代表“假”,非0代表“真”。例如:
若a=4,则 !a 的值为0(因为a为4,非0,被认为是真,对真取反结果为假,假用0表示);
A &&-5 的值为1(因为a 为非0,认为是真,-5也为非0,也是真,真与真,结果仍为真,真用1表示);
4||0 的值为1(因为4为真,0为假,真||假,结果为真,用1表示)。
4.6.3 逻辑运算符的优先级和结合性
在这三种逻辑运算符中,它们的优先级别各不相同。逻辑非(!)的优先级别最高,逻辑与(&&)的优先级高于逻辑或(||)。
如果将前面介绍的算术运算符和关系运算符结合在一起使用时,逻辑非(!)优先级最高,然后是算术运算符、关系运算符、逻辑与(&&)、逻辑或(||)。
比如:5>3 && 2||!8<4-2 等价于 ((5>3) && 2)||((!8)<(4-2)),结果为1。
运算符!的结合性是“右结合”,而&&和||的结合性是“左结合”。
另外,为了让一个复杂的表达式更容易被读懂,在合适的地方使用括号来明确优先级是很有效的方式,虽然它并不是特别有必要。在前面关于开门权限的例子中,现在将第一个部分加个括号,就能使它看起来逻辑更明确和清晰了。
01 if (enteredDoorCode&&passedRetinaScan)|| hasDoorKey || knowsOverridePassword {
02 println("Welcome!") //执行时输出 "Welcome!"
03 }
04 else {
05 println("ACCESSDENIED")
06 }
通过将前面两个逻辑与运算使用括号括起来,使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号并不会影响输出的结果,但对于读代码的人来说有括号就会显得代码更清晰易懂。
4.6.4 逻辑运算范例
本小节通过两个范例来学习逻辑运算符的使用。
【范例4-18】 试写出判断某数x是否小于-2且大于等于5的逻辑表达式。当x值为0时,分析程序运行结果
01 var x:Int,y:Bool //定义整型变量 x,y
02 x=0
03 y=x<-2&& x>=5 //将表达式的值赋给变量 y
04 println("x<-2&& x>=5=\(y)") //输出结果
本范例中判断某数x是否小于-2且大于等于5的逻辑表达式可写为“x<-2 && x>=5”,因为是两个条件同时成立,应使用“&&”运算符将两个关系表达式连接在一起,所以表达式从整体上看是逻辑表达式,而逻辑符左右两边的运算分量又分别是关系表达式。该例应先计算x<-2(不成立,值为1)与x>=5(不成立,值为0),再用1&&0,结果为0。
【范例4-19】 试判断给定的2015年year是否为闰年。闰年的条件是符合下面两个条件之一:①能被4整除,但不能被100整除;②能被400整除。
01 var year=2015 //定义整型变量 year表示年份
02 if(year%4==0&& year% 100!=0||year% 400==0){ //判断 year是否为闰年
03 println("\(year)是闰年 ") //若为闰年则输出 year是闰年
04 }
05 else{
06 println("\(year)不是闰年 \n") //否则输出 year不是闰年
07 }
在本例中,用了3个求余操作表示对某一个数能否整除。通常,我们采用此方法表示某一个量能够被整除。判断year是否为闰年有两个条件,这两个条件是或的关系,第1个条件可表示为:year%4==0&&year%100!=0,第2个条件可表示为:year%400==0。两个条件中间用“||”运算符连接即可。即表达式可表示为:(year%4==0&&year%100!=0)||(year%400==0)。
由于逻辑运算符的优先级高于关系运算符,且!的优先级高于&&,&&的优先级又高于||,因此上式可以将括号去掉写为如下形式。
year % 4==0 && year % 100!=0 || year % 400==0
如果判断year为平年(非闰年),可以写成如下形式。
!(year % 4==0 && year % 100!=0 || year % 400==0)
因为是对整个表达式取反,所以要用圆括号括起来。否则就成了!year % 4==0,由于!的优先级高,会先计算!year,因此后面必须用圆括号括起来。
本例中使用了if-else语句,可理解为:若if后面括号中的表达式成立,则执行println("%d是闰年\n",year);语句,否则执行println("%d不是闰年\n",year);语句。
如果要判断一个变量的a值是否在0到5之间,很自然想到了这样一个表达式。
if(0<a<5)
这个表达式没有什么不正常的,编译通过。但是现在仔细分析一下if语句的运行过程:表达式0<a<5中首先判断0<a,如果a>0则为真,否则为假。
设a的值为3,此时表达式结果为逻辑真,那么整个表达式if(0<a<5)成为if(1<5)(注意这个新的表达式中的1是0<a的逻辑值),这时问题就出现了,可以看到当变量a的值大于0的时候总有1<5,所以后面的<5这个关系表达式是多余的了。另外假设a的值小于0,也会出现这样的情况。由此看来,这样的写法肯定是错的。
正确的写法应该如下。
if((0<a)&&(a<5)) //如果变量a的值大于0并且小于5*/
4.7 高级运算符
本节视频教学录像:15分钟
除了前面所讲的运算符,Swift还有许多复杂的高级运算符,包括了C语言和Objective-C中的位运算符和移位运算。
不同于C语言中的数值计算,Swift的数值计算默认是不可溢出的。溢出行为会被捕获并报告为错误。你可以使用Swift为你准备的另一套默认允许溢出的数值运算符,如可溢出加&+。所有允许溢出的运算符都是以&开始的。
自定义的结构、类和枚举,是否可以使用标准的运算符来定义操作?当然可以!在Swift中,你可以为你创建的所有类型定制运算符的操作。
可定制的运算符并不限于那些预设的运算符,自定义有个性的中置、前置、后置及赋值运算符,当然还有优先级和结合性。这些运算符的实现可以运用预设的运算符,也可以运用之前定制的运算符。
4.7.1 位运算符
位操作符通常在诸如图像处理和创建设备驱动等底层开发中使用,使用它可以单独操作数据结构中原始数据的比特位。在使用一个自定义的协议进行通信的时候,运用位运算符来对原始数据进行编码和解码也是非常有效的。
Swift支持如下所有C语言的位运算符。
1.按位取反运算符
按位取反运算符~是对一个操作数的每一位都取反。
这个运算符是前置的,所以请不加任何空格地写在操作数之前。
01 let initialBits:UInt8= 0b00001111
02 let invertedBits=~ initialBits //等于 0b11110000
UInt8是8位无符整型,可以存储0~255之间的任意数。这个例子初始化一个整型为二进制值00001111(前4位为0,后4位为1),它的十进制值为15。
使用按位取反运算~对initialBits 操作,然后赋值给 invertedBits 这个新常量。这个新常量的值等于所有位都取反的initialBits,即1变成0,0变成1,结果变成了11110000,十进制值为240。
2.按位与运算符
按位与运算符用于对两个数进行操作,然后返回一个新的数,这个数的每个位都需要两个输入数的同一位都为1时才为1。
以下代码,firstSixBits和lastSixBits中间4个位都为1。对它们进行按位与运算后,就得到了00111100,即十进制的60。
01 let firstSixBits:UInt8= 0b11111100
02 let lastSixBits:UInt8= 0b00111111
03 letmiddleFourBits= firstSixBits& lastSixBits //等于 00111100
3.按位或运算
按位或运算符|用于比较两个数,然后返回一个新的数,这个数的每一位设置1的条件是两个输入数的同一位都不为 0(即任意一个为1,或都为1)。
下面的代码,someBits和moreBits在不同位上有1。按位或运行的结果是11111110,即十进制的254。
01 let someBits:UInt8= 0b10110010
02 letmoreBits:UInt8= 0b01011110
03 let combinedbits= someBits |moreBits //等于 11111110
4.按位异或运算符
按位异或运算符^用于比较两个数,然后返回一个数,这个数的每个位设为1的条件是两个输入数的同一位不同,如果相同就设为0。
以下代码,firstBits和otherBits 都有一个1跟另一个数不同的。所以按位异或的结果是把它置为1,其他都置为0。
01 let firstBits:UInt8= 0b00010100
02 let otherBits:UInt8= 0b00000101
03 let outputBits= firstBits^ otherBits //等于 00010001
按位左移运算符<<和按位右移运算符>>会把一个数的所有比特位按以下定义的规则向左或向右移动指定位数。
向左移动一个比特位相当于把这个数乘以2,向右移一位就是除以2。
5.无符整型的移位操作
对无符整型的移位的效果如下。
已经存在的比特位向左或向右移动指定的位数,被移出整型存储边界的位数直接抛弃,移动留下的空白位用0来填充,这种方法称为逻辑移位。
下图展示了 11111111 << 1(11111111 向左移 1 位),和11111111>>1(11111111向右移1位)。灰色的1是被抛弃的,0 是被填充进来的,其余的1是被移位的。
01 let shiftBits:UInt8= 4 //即二进制的 00000100
02 shiftBits<< 1 // 00001000
03 shiftBits<< 2 // 00010000
04 shiftBits<< 5 // 10000000
05 shiftBits<< 6 // 00000000
06 shiftBits>> 2 // 00000001
你可以使用移位操作进行其他数据类型的编码和解码。
01 letpink:UInt32= 0xCC6699
02 let redComponent= (pink& 0xFF0000)>> 16 // redComponent是 0xCC即204
03 letgreenComponent= (pink& 0x00FF00)>> 8 //greenComponent是0x66即102
04 letblueComponent=pink& 0x0000FF //blueComponent是 0x99即153
这个例子使用了一个UInt32的命名为pink的常量来存储层叠样式表CSS中粉色的颜色值,CSS颜色#CC6699在Swift中用十六进制0xCC6699来表示,然后使用按位与(&)和按位右移就可以从这个颜色值中解析出红(CC)、绿(66)、蓝(99)3个部分。
对0xCC6699和0xFF0000进行按位与&操作就可以得到红色部分。0xFF0000 中的0了遮盖了0xCC6699 的第二和第三个字节,这样 6699 被忽略了,只留下 0xCC0000。然后,按向右移动16位,即>>16。十六进制中每两个字符是 8 比特位,所以移动16位的结果是把 0xCC0000 变成 0x0000CC。这和0xCC是相等的,都是十进制的204。
同样的,绿色部分来自于0xCC6699和0x00FF00 的按位操作,得到0x006600。然后向右移动8,得到0x66,即十进制的102。
最后,蓝色部分对0xCC6699和0x0000FF进行按位与运算,得到0x000099,无需向右移位了,所以结果就是0x99,即十进制的153。
6.有符整型的移位操作
有符整型的移位操作相对复杂,因为正负号也是用二进制位表示的。(这里举的例子虽然都是8位的,但它的原理是通用的。)
有符整型通过第1个比特位(称为符号位)来表达这个整数是正数还是负数。0代表正数,1代表负数。其余的比特位(称为数值位)存储其实值。有符正整数和无符正整数在计算机里的存储结果是一样的,下面我们来看+4 内部的二进制结构。
现在符号位为1,代表负数,7个数值位要表达的二进制值是124,即128 - 4。
负数的编码方式用二进制补码表示。这种表示方式看起来很奇怪,但它有几个优点。
首先,只需要对全部 8 个比特位(包括符号)做标准的二进制加法就可以完成-1 +-4 的操作,忽略加法过程产生的超过 8 个比特位表达的任何信息。
其次,由于使用二进制补码表示,我们可以和正数一样对负数进行按位左移右移的,同样也是左移1 位时乘以2,右移1位时除以2。要达到此目的,对有符整型的右移有一个特别的要求:对有符整型按位右移时,使用符号位(正数为0,负数为1)填充空白位。
这就确保了在右移的过程中,有符整型的符号不会发生变化,这称为算术移位。正因为正数和负数特殊的存储方式,向右移位使它接近于0。移位过程中保持符号不变,负数在接近0的过程中一直是负数。
4.7.2 溢出运算符
默认情况下,当一个整型常量或变量被赋予一个它不能承载的大值时,Swift会禁止,它会报错。这样,在操作过大或过小的数的时候就很安全了。
例如,Int16 整型能承载的整数范围是-32768 到 32767,如果被赋予超过这个范围的值,就会报错。
01 varpotentialOverflow= Int16.max
02 //potentialOverflow等于 32767,这是 Int16能承载的最大整数
03 potentialOverflow+= 1
04 //噢 ,出错了
对过大或过小的数值进行错误处理会让你的数值边界条件更灵活。
当然,当你有意在溢出时对有效位进行截断,可采用溢出运算,而非错误处理。Swfit 为整型计算提供了 5 个&符号开头的溢出运算符。
溢出加法&+
溢出减法&-
溢出乘法&*
溢出除法&/
溢出求余&%
1.值的上溢出
下面的例子使用了溢出加法&+来解析无符整数的上溢出。
01 varwillOverflow=UInt8.max //willOverflow等于UInt8的最大整数 255
02 willOverflow=willOverflow&+ 1 //这时候willOverflow等于 0
willOverflow用Int8所能承载的最大值255(二进制 11111111),然后用&+加1。此时,UInt8就无法表达这个新值的二进制了,也就导致了这个新值的上溢出,可以参见下图。溢出后,新值在UInt8的承载范围内的那部分是 00000000,也就是0。
2.值的下溢出
数值也有可能因为太小而越界。举例如下。
UInt8 的最小值是 0(二进制为 00000000)。使用&-进行溢出减1,就会得到二进制的11111111, 即十进制的 255。
Swift中的代码如下。
01 varwillUnderflow=UInt8.min //willUnderflow等于UInt8的最小值 0
02 willUnderflow=willUnderflow&- 1 //此时willUnderflow等于 255
有符整型也有类似的下溢出,有符整型所有的减法也都是对包括在符号位在内的二进制数进行二进制减法的,这在“按位左移/右移运算符”一节提到过。最小的有符整数是-128,即二进制的10000000。用溢出减法减去1后,变成了 01111111,即UInt8所能承载的最大整数 127。
Swift中的代码如下。
01 var signedUnderflow= Int8.min // signedUnderflow等于最小的有符整数 -128
02 signedUnderflow= signedUnderflow&- 1 //现在 signedUnderflow等于 127
3.除零溢出
一个数除于0 i /0,或者对0求余数 i%0,就会产生一个错误。
01 let x= 1
02 let y= x / 0
使用它们对应的可溢出的版本的运算符&/和&%进行除 0 操作时就会得到 0 值。
01 let x= 1
02 let y= x&/ 0 // y等于 0
4.7.3 运算符的优先级与合理性
运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。相同优先级的运算符在一起时是怎么组合或关联的,是和左边的一组呢,还是和右边的一组?意思就是,到底是和左边的表达式结合呢,还是和右边的表达式结合?
在混合表达式中,运算符的优先级和结合性是非常重要的。举个例子,为什么下列表达式的结果为4?
01 2+ 3 * 4% 5 //结果是 4
如果严格地从左计算到右,计算过程会如下所示。
01 2plus 3 equals 5; // 2+ 3= 5
02 5 times 4 equals 20; // 5 * 4= 20
03 20 remainder 5 equals 0 // 20 / 5= 4余0
但是正确答案是4而不是0。优先级高的运算符要先计算,在Swift和C语言中,都是先乘除后加减的。所以,执行完乘法和求余运算才能执行加减运算。
乘法和求余拥有相同的优先级,在运算过程中,我们还需要使用结合性,乘法和求余运算都是左结合的。这相当于在表达式中有隐藏的括号让运算从左开始。
01 2+ ((3 * 4)% 5)
02 (3 * 4) is 12, so this is equivalent to: 3 * 4= 12,所以这相当于:2+ (12% 5)
03 (12% 5) is 2, so this is equivalent to: 12% 5= 2,所以这又相当于:2+ 2
计算结果为4。
提示
Swift的运算符较C语言和Objective-C来得更简单和保守,这意味着跟基于C的语言可能不一样。所以,在移植已有代码到Swift时,要注意确保代码按你想的那样去执行。
4.7.4 运算符函数
让已有的运算符对自定义的类和结构进行运算,这称为运算符重载。
下面的例子展示了如何用+让一个自定义的结构做加法。算术运算符+是一个二目运算符,因为它有两个操作数,而且它必须出现在两个操作数之间。
例子中定义了一个名为Vector2D的二维坐标向量(x,y)的结构,然后定义了让两个Vector2D 的对象相加的运算符函数。
01 structVector2D {
02 var x= 0.0, y= 0.0
03 }
04 @infix func+ (left:Vector2D, right:Vector2D) ->Vector2D {
05 returnVector2D(x: left.x+ right.x, y: left.y+ right.y)
06 }
该运算符函数定义了一个全局的+函数,这个函数需要两个Vector2D类型的参数,返回值也是Vector2D类型。需要定义和实现一个中置运算的时候,在关键字 func 之前写上属性@infix 就可以了。
在这个代码实现中,参数被命名为了left和right,代表+左边和右边的两个Vector2D对象。函数返回了一个新的Vector2D的对象,这个对象的x和y分别等于两个参数对象的x和y的和。这个函数是全局的,而不是Vector2D结构的成员方法,所以任意两个Vector2D对象都可以使用这个中置运算符。
01 let vector=Vector2D(x: 3.0, y: 1.0)
02 let anotherVector=Vector2D(x: 2.0, y: 4.0)
03 let combinedVector= vector+ anotherVector
04 // combinedVector是一个新的Vector2D,值为 (5.0, 5.0)
这个例子实现两个向量(3.0,1.0)和(2.0,4.0)相加,得到向量(5.0,5.0)的过程,如下图所示。
4.7.5 前置和后置运算符
上个例子演示了一个二目中置运算符的自定义实现,同样我们也可以完成标准单目运算符的实现。单目运算符只有一个操作数,在操作数之前就是前置的,如-a;在操作数之后就是后置的,如i++。
实现一个前置或后置运算符时,在定义该运算符的时候需要在关键字func之前标注@prefix或@postfix属性。
01 @prefix func - (vector:Vector2D) ->Vector2D {
02 returnVector2D(x: -vector.x, y: -vector.y)
03 }
这段代码为Vector2D类型提供了单目减运算-a,@prefix属性表明这是个前置运算符。对于数值,一目减运算符可以把正数变负数,把负数变正数。对于Vector2D,单目减运算将其x和y都进行了单目减运算。
01 letpositive=Vector2D(x: 3.0, y: 4.0)
02 let negative= -positive // negative为 (-3.0, -4.0)
03 let alsoPositive= -negative // alsoPositive为 (3.0, 4.0)
4.7.6 组合赋值运算符
组合赋值是其他运算符和赋值运算符一起执行的运算,如+=把加运算和赋值运算组合成一个操作。实现一个组合赋值符号需要使用@assignment属性,还需要把运算符的左参数设置成inout,因为这个参数会在运算符函数内直接修改它的值。
01 @assignment func+= (inout left:Vector2D, right:Vector2D) {
02 left= left+ right
03 }
因为加法运算在之前定义过了,这里无需重新定义。所以,加赋运算符函数使用已经存在的高级加法运算符函数来执行左值加右值的运算。
01 var original=Vector2D(x: 1.0, y: 2.0)
02 let vectorToAdd=Vector2D(x: 3.0, y: 4.0)
03 original+= vectorToAdd // original现在为 (4.0, 6.0)
可以将@assignment属性和@prefix或@postfix属性组合起来,实现一个Vector2D的前置运算符。
01 @prefix@assignment func++ (inout vector:Vector2D) ->Vector2D {
02 vector+=Vector2D(x: 1.0, y: 1.0)
03 return vector
04 }
这个前置使用了已经定义好的高级加赋运算,将自己加上一个值为 (1.0,1.0) 的对象然后赋给自己,然后再将自己返回。
01 var toIncrement=Vector2D(x: 3.0, y: 4.0)
02 let afterIncrement=++toIncrement
03 // toIncrement现在是 (4.0, 5.0)
04 // afterIncrement现在也是 (4.0, 5.0)
提示
默认的赋值符是不可重载的,只有组合赋值符可以重载。三目条件运算符 a?b:c也是不可重载。
4.7.7 比较运算符
Swift并不知道自定义类型是否相等或不等,因为等于或者不等于由代码说了算。所以自定义的类和结构要使用比较符==或!=就需要重载。定义相等运算符函数跟定义其他中置运算符相同。
01 @infix func== (left:Vector2D, right:Vector2D) ->Bool {
02 return (left.x== right.x)&& (left.y== right.y)
03 }
04 @infix func != (left:Vector2D, right:Vector2D) ->Bool {
05 return !(left== right)
06 }
上述代码实现了相等运算符==来判断两个 Vector2D 对象是否有相等的值,相等的概念就是它们有相同的x值和相同的y值,我们就用这个逻辑来实现。接着使用==的结果实现了不相等运算符!=。
现在我们可以使用这两个运算符来判断两个Vector2D对象是否相等。
01 let twoThree=Vector2D(x: 2.0, y: 3.0)
02 let anotherTwoThree=Vector2D(x: 2.0, y: 3.0)
03 if twoThree== anotherTwoThree {
04 println("这两个向量是相等的 .")
05 }
4.7.8 自定义运算符
如果标准的运算符不够使用,Swift还支持自定义声明一些个性化的运算符,以供在程序开发过程中使用,但个性的运算符只能使用这些字符/=-+*%<> ! &|^~,其他字符 Swift 是不支持的。
1.自定义中置运算符的使用
自定义运算符必须在全局域中使用operator关键字来声明,可以声明为前置、中置或后置的。
operator prefix +++{}
这段代码定义了一个新的前置运算符叫+++,此前Swift并不存在这个运算符。此处为了演示,我们让+++对Vector2D对象的操作定义为双自增这样一个独有的操作,这个操作使用了之前定义的加赋运算实现了自已加上自己然后返回的运算。
01 @prefix@assignment func+++ (inout vector:Vector2D) ->Vector2D {
02 vector+= vector
03 return vector
04 }
Vector2D的+++的实现和++的实现很接近,唯一不同的是前者是加自己,后者是加值为(1.0,1.0)的向量。
01 var toBeDoubled=Vector2D(x: 1.0, y: 4.0)
02 let afterDoubling=+++toBeDoubled
03 // toBeDoubled现在是 (2.0, 8.0)
04 // afterDoubling现在也是 (2.0, 8.0)
2.自定义中置运算符的优先级和结合性
可以为自定义的中置运算符指定优先级和结合性。具体可以看看优先级和结合性解释这两个因素是如何影响多种中置运算符混合的表达式的计算的。
结合性(associativity)的值可取的值有left,right和none。左结合运算符跟其他优先级相同的左结合运算符写在一起时,会跟左边的操作数结合。同理,右结合运算符会跟右边的操作数结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。
结合性(associativity)的值默认为 none,优先级(precedence)默认为 100。
下面的例子定义了一个新的中置符+-,是左结合的 left,优先级为 140。
01 operator infix+- { associativity leftprecedence 140 }
02 func+- (left:Vector2D, right:Vector2D) ->Vector2D {
03 returnVector2D(x: left.x+ right.x, y: left.y - right.y)
04 }
05 let firstVector=Vector2D(x: 1.0, y: 2.0)
06 let secondVector=Vector2D(x: 3.0, y: 4.0)
07 letplusMinusVector= firstVector+- secondVector
08 //plusMinusVector此时的值为 (4.0, -2.0)
这个运算符把两个向量的x相加,把两个向量的y相减。因为它实际是属于加减运算,所以让它保持了和加法一样的结合性和优先级(left 和 140)。
4.7.9 类型转换运算符(Type-Casting Operators)
有两种类型转换操作符:as和is。它们有如下的形式。
as 运算符会把目标表达式转换成指定的类型(specified type),过程如下。
⑴如果类型转换成功,那么目标表达式就会返回指定类型的实例(instance),例如把子类(subclass)变成父类(superclass)时。
⑵如果转换失败,则会抛出编译时错误(compile-time error)。
⑶如果上述两个情况都不是(也就是说,编译器在编译时期无法确定转换能否成功),那么目标表达式就会变成指定的类型的optional(指定类型中的可选类型)。然后在运行时,如果转换成功,目标表达式就会作为optional的一部分来返回;否则,目标表达式返回nil。对应的例子是:把一个superclass转换成一个subclass。
01 classSomeSuperType {}
02 classSomeType:SomeSuperType {}
03 classSomeChildType:SomeType {}
04 let s=SomeType()
05 let x= s asSomeSuperType //如果成功类型是SomeSuperType
06 let y= s as Int //如果失败compile-time error
07 let z= s asSomeChildType //运行时如果失败类型是SomeChildType
对于编译器来说,使用“as”做类型转换跟正常的类型声明是一样的。举例如下。
01 let y1= x asSomeType // Type information from 'as'
02 let y2:SomeType= x // Type information from an annotation
“is”运算符在运行时(runtime)会做检查,成功会返回true,否则返回false。
上述检查在编译时(compile time)不能使用。例如下面的使用是错误的。
01 "hello" isString
02 "hello" is Int
类型转换的语法如下。
type-casting-operator → is type as? opt type
4.8 表达式
本节视频教学录像:13分钟
在Swift中存在4种表达式,分别是:前缀(prefix)表达式、二元(binary)表达式、主要(primary)表达式和后缀(postfix)表达式。表达式可以返回一个值或者逻辑运算结果。
前缀表达式和二元表达式就是对某些表达式使用各种运算符(operators)。主要表达式是最短小的表达式,它提供了获取(变量的)值的一种途径。后缀表达式则允许你建立复杂的表达式,例如配合函数调用和成员访问。
4.8.1 前缀表达式
前缀表达式由前缀符号和表达式组成(这个前缀符号只能接收一个参数)。Swift标准库支持如下的前缀操作符。
++自增 1(increment)
--自减 1(decrement)
! 逻辑否(Logical NOT )
~按位否(Bitwise NOT )
+加(Unary plus)
-减(Unary minus)
作为对上面标准库运算符的补充,也可以对某个函数的参数使用“&”运算符。前缀表达式的语法如下。
prefix-expression → prefix-operator opt postfix-expression
prefix-expression → in-out-expression
in-out-expression → & identifier
4.8.2 二元表达式
二元表达式由"左边参数"+"二元运算符"+"右边参数"组成,它有如下的形式。
Left-hand argument operator right-hand argument
Swift 标准库提供了如下的二元运算符。
级求幂相关(无结合,优先级160)
<<按位左移(Bitwise left shift)
>>按位右移(Bitwise right shift)
级乘除法相关(左结合,优先级150)
*乘
/除
%求余
&*乘法,忽略溢出(Multiply, ignoring overflow)
&/除法,忽略溢出(Divide, ignoring overflow)
&%求余, 忽略溢出(Remainder, ignoring overflow)
&位与(Bitwise AND)
加减法相关(左结合,优先级140)
+加
-减
&+ Add with overflow
&- Subtract with overflow
|按位或(Bitwise OR)
^按位异或(Bitwise XOR)
Range (无结合,优先级135)
.. 半闭值域 Half-closed range
... 全闭值域 Closed range
类型转换 (无结合,优先级132)
is 类型检查(type check)
as 类型转换(type cast)
Comparative (无结合,优先级130)
<小于
<=小于等于
>大于
>=大于等于
==等于
!=不等
===恒等于
!==不恒等
~=模式匹配(Pattern match)
合取(Conjunctive)(左结合,优先级120)
&&逻辑与(Logical AND)
析取(Disjunctive)(左结合,优先级110)
||逻辑或(Logical OR)
三元条件(Ternary Conditional)(右结合,优先级100)
?:三元条件 Ternary conditional
赋值 (Assignment)(右结合,优先级90)
=赋值(Assign)
*= Multiply and assign
/= Divide and assign
%= Remainder and assign
+= Add and assign
-= Subtract and assign
<<= Left bit shift and assign
= Right bit shift and assign
&= Bitwise AND and assign
^= Bitwise XOR and assign
|= Bitwise OR and assign
&&= Logical AND and assign
||= Logical OR and assign
提示
在解析时,一个二元表达式表示为一个一级数组(a flat list), 这个数组(List)根据运算符的先后顺序,被转换成了一个tree。例如,2 + 3 5 首先被认为是:2、+、3、5,随后它被转换成tree(2 +(3*5))。
二元表达式的语法如下。
binary-expression →binary-operatorprefix-expression
binary-expression →assignment-operatorprefix-expression
binary-expression →conditional-operatorprefix-expression
binary-expression →type-casting-operator
binary-expressions →binary-expressionbinary-expressions opt
4.8.3 赋值表达式
赋值表达式会对某个给定的表达式赋值。它有如下的形式。
01 Expression=value
就是把右边的value赋值给左边的expression。如果左边的expression需要接收多个参数(是一个tuple),那么右边必须也是一个具有同样数量参数的tuple(允许嵌套的tuple)。
01 (a, _, (b, c))= ("test", 9.45, (12, 3))
02 // a is "test",b is 12, c is 3, and 9.45 is ignored
赋值运算符不返回任何值。
赋值表达式的语法如下。
assignment-operator →=
4.8.4 主要表达式
主要表达式是最基本的表达式,它可以跟前缀表达式、二元表达式、后缀表达式以及其他主要表达式组合使用。
4.8.5 字符型表达式
字符型表达式由这些内容组成:普通的字符(string, number),一个字符的字典或者数组,或者下面列表中的特殊字符。
4.8.6 self表达式
self 表达式是对当前type或者当前instance的引用。它的形式如下。
self
self.member name
self[subscript index]
self(initializer arguments)
self.init(initializer arguments)
如果在initializer、subscript、instance method中,self等同于当前type的instance;在一个静态方法(static method)、类方法(class method)中,self等同于当前的type。当访问member(成员变量)时,self用来区分重名变量(如函数的参数)。例如,下面的self.greeting指的是var greeting:String,而不是init(greeting:String)。
01 classSomeClass {
02 vargreeting:String
03 init(greeting:String) {
04 self.greeting=greeting
05 }
06 }
在 mutating 方法中,你可以使用self对该instance进行赋值。
01 structPoint {
02 var x= 0.0, y= 0.0
03 mutating funcmoveByX(deltaX:Double, ydeltaY:Double) {
04 self=Point(x: x+deltaX, y: y+deltaY)
05 }
06 }
4.8.7 超类表达式
超类表达式可以使我们在某个class中访问它的超类。它有如下形式。
形式 1 用来访问超类的某个成员(member)实现。
形式 2 用来访问该超类的subscript实现。
形式 3 用来访问该超类的initializer实现。
子类(subclass)可以通过超类(superclass)表达式在它们的 member, subscripting 和initializers中利用超类中的某些实现(既有的方法或者逻辑)。
4.8.8 闭包表达式
闭包(closure)表达式可以建立一个闭包,在其他语言中也叫lambda,或者匿名函数(anonymous function)。跟函数(function)的声明一样,闭包(closure)包含了可执行的代码[跟方法主体(statement)类似]以及接收(capture)的参数。它的形式如下。
01 {(parameters)->return type in
02 Sratements
03 }
闭包的参数声明形式跟方法中的声明一样。
闭包还有几种特殊的形式,可以让使用更加简洁。
⑴闭包可以省略它的参数的type和返回值的type。如果省略了参数和参数类型,也要省略“in”关键字。如果被省略的type无法被编译器获知(inferred),那么就会抛出编译错误。
⑵闭包可以省略参数,转而在方法体(statement)中使用0、1、$2来引用出现的第一个、第二个、第三个参数。
⑶如果闭包中只包含了一个表达式,那么该表达式就会自动成为该闭包的返回值。在执行“type inference”时,该表达式也会返回。下面几个闭包表达式是等价的。
01 myFunction {
02 (x: Int, y: Int) -> Int in
03 return x+ y
04 }
05
06 myFunction {
07 (x, y) in
08 return x+ y
09 }
10 .
11 myFunction { return $0+ $1 }
12
13 myFunction { $0+ $1 }
闭包表达式可以通过一个参数列表(capture list)来显式指定它需要的参数。参数列表由中括号[]括起来,里面的参数由逗号“,”分隔。一旦使用了参数列表,就必须使用“in”关键字(在任何情况下都得这样做,包括忽略参数的名字、type、返回值时等)。
在闭包的参数列表(capture list)中,参数可以声明为“weak”或者“unowned”。
01 myFunction {print(self.title) } // strong capture
02 myFunction { [weak self] inprint(self!.title) } //weak capture
03 myFunction { [unowned self] inprint(self.title) } // unowned capture
在参数列表中,也可以使用任意表达式来赋值。该表达式会在闭包被执行时赋值,然后按照不同的力度来获取(这句话请慎重理解),举例如下。
01 //Weak capture of "self.parent" as "parent"
02 myFunction { [weakparent= self.parent] inprint(parent!.title) }
4.8.9 隐式成员表达式
在可以判断出类型(type)的上下文(context)中,隐式成员表达式是访问某个type的member(例如 class method、enumeration case)的简洁方法。它的形式如下。
01 var x=MyEnumeration.SomeValue
02 x= .AnotherValue
隐式成员表达式的语法如下。
implicit-member-expression →.identifier
圆括号表达式(Parenthesized Expression)由多个子表达式和逗号“,”组成。每个子表达式前面可以有identifier x:这样的可选前缀。其形式如下。
圆括号表达式用来建立tuples,然后把它作为参数传递给function。如果某个圆括号表达式中只有一个子表达式,那么它的type就是子表达式的type。举例如下。
(1)的type是Int,而不是(Int)圆括号表达式的语法。
parenthesized-expression → ( expression-element-list opt )
expression-element-list → expression-element | expression-element , expression-element-list
expression-element → expression | identifier : expression
通配符表达式(Wildcard Expression)用来忽略传递进来的某个参数。例如,下面的代码中,10被传递给x,20被忽略。
01 (x, _)= (10, 20)
02 // x is 10, 20 is ignored
通配符表达式的语法如下。
wildcard-expression →_
4.8.10 后缀表达式
后缀表达式就是在某个表达式的后面加上操作符。严格地讲,每个主要表达式(primary expression)都是一个后缀表达式。
Swift 标准库提供了下列后缀表达式。
++ Increment
-- Decrement
4.8.11 函数调用表达式
函数调用表达式由函数名和参数列表组成。它的形式如下。
如果该function的声明中指定了参数的名字,那么在调用的时候也必须将其写出来。例如:可以在函数调用表达式的尾部(最后一个参数之后)加上一个闭包(closure),该闭包会被目标函数理解并执行。它具有如下两种写法。
01 someFunction(x, {$0== 13})
02 someFunction(x) {$0== 13}
如果闭包是该函数的唯一参数,那么圆括号可以省略。
01 myData.someMethod() {$0== 13}
02 myData.someMethod {$0== 13}
函数调用表达式的语法如下。
function-call-expression→ postfix-expressionparenthesized-expression
function-call-expression→ postfix-expressionparenthesized-expressionopt trailing-closure
trailing-closure→ closure-expression
初始化函数表达式(InitializerExpression)用来给某个Type初始化。它的形式如下。
跟函数(function)不同,initializer不能返回值。
01 var x=SomeClass.someClassFunction // ok
02 var y=SomeClass.init // error
也可以使用初始化函数表达式来委托给父类的initializer。
01 classSomeSubClass:SomeSuperClass {
02 init() {
03 // subclass initializationgoes here
04 super.init()
05 }
06 }
initializer 表达式的语法如下。
initializer-expression → postfix-expression .init
显式成员表达式(Explicit Member Expression)允许我们访问type、tuple、module、的成员变量,它的形式如下。
该member就是某个type在声明时所定义(declaration or extension)的变量,举例如下。
01 classSomeClass {
02 var someProperty= 42
03 }
04 let c=SomeClass()
05 let y= c.someProperty //Member access
对于tuple,要根据它们出现的顺序(0,1,2...)来使用。
01 var t= (10, 20, 30)
02 t.0= t.1
03 //Now t is (20, 20, 30)
显示成员表达式的语法如下。
explicit-member-expression → postfix-expression . decimal-digit
explicit-member-expression → postfix-expression . identifiergeneric-argument-clause opt
后缀表达式( Postfix Self Expression )由某个表达式+“self'”组成,形式如下。
形式 1 表示会返回 expression 的值。例如:x.self 返回 x。
形式 2 返回对应的 type。我们可以用它来动态获取某个 instance 的 type。
后缀 self 表达式的语法如下。
postfix-self-expression → postfix-expression .self
Dynamic Type表达式(Dynamic Type Expression)(因为 dynamicType 是一个独有的方法,所以这里保留了英文单词,未作翻译,类似于 self expression)由某个表达式 +“dynamicType”组成。
上面的形式中,expression不能是某type的名字(当然了,如果我都知道它的名字了,还需要动态来获取它吗)。动态类型表达式会返回“运行时”某个instance的type,具体请看下面的列子。
01 classSomeBaseClass {
02 class funcprintClassName() {
03 println("SomeBaseClass")
04 }
05 }
06 classSomeSubClass:SomeBaseClass {
07 override class funcprintClassName() {
08 println("SomeSubClass")
09 }
10 }
11 let someInstance:SomeBaseClass=SomeSubClass()
12 // someInstance is of typeSomeBaseClass at compile
13 // someInstance is of typeSomeSubClass at runtime
14 someInstance.dynamicType.printClassName()
15 //prints "SomeSubClass"
Dynamic Type表达式的语法如下。
dynamic-type-expression → postfix-expression .dynamicType
附属脚本表达式(SubscriptExpression)提供了通过附属脚本访问getter/setter的方法。它的形式如下。
附属脚本表达式可以通过getter获取某个值,或者通过setter赋予某个值。
附属脚本表达式的语法如下。
subscript-expression → postfix-expression [ expression-list ]
强制取值表达式(Forced- Value Expression)用来获取某个目标表达式的值(该目标表达式的值必须不是nil)。它的形式如下。
如果该表达式的值不是nil,则返回对应的值。否则,抛出运行时错误(runtime error)。
强制取值表达式的语法如下:
forced-value-expression→postfix-expression!
可选链表达式(Optional- Chaining Expression)由目标表达式+“?”组成,形式如下。
后缀“?”返回目标表达式的值,把它作为可选的参数传递给后续的表达式。
如果某个后缀表达式包含了可选链表达式,那么它的执行过程就比较特殊:首先先判断该可选链表达式的值,如果是nil,整个后缀表达式都返回nil;如果该可选链的值不是nil,则正常返回该后缀表达式的值(依次执行它的各个子表达式)。在这两种情况下,该后缀表达式仍然是一个optional type(In either case, the value of the postfix expression is still of anoptional type)。如果某个“后缀表达式”的“子表达式”中包含了“可选链表达式”,那么只有最外层的表达式返回的才是一个optional type。例如,在下面的例子中,如果c不是nil,那么c?.property.performAction()这句代码在执行时,就会先获得c的property方法,然后调用performAction()方法。对于“c?.property.performAction()”这个整体,它的返回值是一个optionaltype。
01 var c:SomeClass?
02 var result:Bool?= c?.property.performAction()
如果不使用可选链表达式,那么上面例子的代码跟下面例子等价。
01 if let unwrappedC = c {
02result = unwrappedC.property.performAction()
03 }
可选链表达式的语法如下。
optional-chaining-expression→postfix-expression
4.9 习题
一、选择
1.下列不属于算术运算符的选项是( )。
A. +
B. -
C. >
D. %
2.下列不属于逻辑运算符的是( )。
A. &&
B. =
C. ||
D. !
3.每个比较运算都会返回一个布尔值,那么1 < 2返回的值是( )。
A. 0
B. true
C. 1
D. false
4.三目运算1<2?1:2>3?2:3返回的值是( )。
A. 1
B. 2
C. 3
D. 0
5.按位取反运算~01010001的结果是( )。
A. 01011110
B. 10100001
C. 10101110
D. 10011101
6.定义Int类型的变量a和b,下列选项中,a的值是b的十进制百位数字的选项是( )。
A.a=b/10%100
B.a=b%100
C.a=b%10%10
D.a=b/100%10
二、填空
1.在算术运算符中,可以实现自加1运算的运算符是______。
2.闭区间运算符的格式为a______b,半闭区间运算符的格式是a______b。
3.(1<2 && 3>4)返回的布尔值是______,(1<2‖4>3)返回的布尔值是______。
4.按位与运算10010011 & 01001010的结果是______。