Swift开发实战
上QQ阅读APP看书,第一时间看更新

第4章 运算符

即使有了变量和常量,也不能进行日常程序处理,还必须用某种方式来将变量、常量的关系表示出来,此时运算符和表达式便应运而生。通过专用的运算符和表达式,可以实现对变量和常量的处理,以实现现实中的项目需求。这样就可以对变量和常量进行必要的运算处理,来实现特定的功能。在本章中,将详细介绍Swift语言中运算符的基本知识,为读者步入本书后面的学习打下坚实的基础。

4.1 运算符概述

在 Swift 语言中,运算符是检查、改变、合并值的特殊符号或短语。例如,通过加号“+”将两个数相加。

    let i = 1 + 2;

复杂些的运算,例如逻辑与运算符&&,如下:

    if enteredDoorCode && passedRetinaScan

让i值加1的便捷自增运算符是++i。

Swift支持大部分标准C语言的运算符,并且改进了许多特性来减少常规编码错误。例如,赋值运算符“=”不返回值,以防止把想要判断相等运算符“==”的地方写成赋值符导致的错误。数值运算符(+、-、*、/、%等)会检测且不允许值溢出,以此避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然,允许使用 Swift 的溢出运算符来实现溢出。

区别于C语言,在Swift 中可以对浮点数进行取余运算(%),Swift 还提供了C语言中没有的表达两数之间的值的区间运算符,如“a..b”和“a...b”,这方便表达一个区间内的数值。

在Swift语言中,可以将运算符分为如下类别。

(1)算术运算符:用于各类数值运算,包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)等。

(2)关系运算符:用于比较运算,包括大于(>)、小于(<)、等于(==)、大于等于(>=)、小于等于(<=)和不等于(!=)等。

(3)逻辑运算符:用于逻辑运算,包括与(&&)、或(||)、非(!)等。

(4)位操作运算符:参与运算的量按二进制位进行运算。

(5)赋值运算符:用于赋值运算。

(6)条件运算符:这是一个3元运算符,用于条件求值(?:)。

(7)逗号运算符:用于把若干表达式组合成一个表达式(,)。

在Swift语言中,根据操作对象的个数可以将运算符分为3种,分别是一元运算、二元运算和三元运算符,具体说明如下所示。

□ 一元运算符:对单一操作对象进行操作(如-a)。一元运算符分前置运算符和后置运算符,前置运算符需紧排操作对象之前(如!b),后置运算符需紧跟操作对象之后(如i++)。

□ 二元运算符:操作两个操作对象(如2 + 3),是中置的,因为它们出现在两个操作对象之间。

□ 三元运算符:操作3个操作对象。和C语言一样,Swift 只有一个三元运算符,就是三元条件运算符(a ? b : c)。

在Swift语言中,受运算符影响的值叫操作数,在表达式1 + 2中,加号“+”是二元运算符,它的两个操作数是值1和2。

4.2 赋值运算符

Swift 语言的赋值运算符包括基本赋值运算符和复合赋值运算符两种,赋值运算符的含义是给某变量或表达式赋值,相当于直接给你一个值。在本节中,将详细讲解赋值运算符和赋值表达式的基本知识。

4.2.1 基本赋值运算符

在Swift语言中,赋值运算(a = b),表示用b的值来初始化或更新a的值。Swift语言的基本赋值运算符记为“=”,由“=”连接的式子称为赋值表达式。其一般使用格式如下所示。

    变量=表达式

例如,下面的都是基本赋值处理:

    let b = 10
    var a = 5
    a = b
    // a 现在等于 10

赋值表达式的功能是计算表达式的值再赋予左边的变量。赋值运算符具有向右结合性,所以a=b=c=10可以理解为a=(b=(c=10))。

在Swift语言中,如果赋值的右边是一个多元组,它的元素可以立即被分解成多个变量。例如,如下所示的演示代码。

    let (x, y) = (1, 2)
    // 现在 x 等于 1, y 等于 2

与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值,以下代码是错误的。

    if x = y {
        // 此句错误, 因为 x = y 并不返回任何值
    }

Swift语言的这个特性使开发者无法把(==)错写成(=),由于if x = y是错误代码,Swift 从底层帮你避免了这些错误代码。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var str = "hello"       //无类型,即自动识别类型
    var s:String = "World"    //字符串类型
    var i:Int = 100         //int类型
    var words:String = "http://www.toppr.net"//
    println(str)
    println(i)
    println(words)

本实例执行后的效果如图4-1所示。

图4-1 执行效果

4.2.2 复合赋值运算符

为了简化程序并提高编译效率,Swift语言允许在赋值运算符“=”之前加上其他运算符,这样就构成了复合赋值运算符。复合赋值运算符的功能是,对赋值运算符左、右两边的操作对象进行指定的算术运算,再将运算结果赋予左边的变量。使用复合赋值运算符的具体格式如下所示。

    算术运算符=

例如,加赋运算“+=”就是复合赋值的一个例子。

    var a = 1
    a += 2 // a 现在是 3

表达式a += 2是a = a + 2的简写,一个加赋值运算就把加法和赋值两件事完成了。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var num1, num2, num3 :Int
    num1=10
    num2=10
    num3=10
        num1 += 2
        num2*=num3
    println(num1)
    println(num2)
    println(num3)
    println(num1+num2+num3)

本实例执行后的效果如图4-2所示。

图4-2 执行效果

注意

复合赋值运算没有返回值,let b = a += 2这类代码是错误。这不同于上面提到的自增和自减运算符。

4.3 算术运算符

算术表达式是指,用算术运算符和括号将操作对象(也称操作数)连接起来的式子。在 Swift语言中有如下几种算术运算符。

□ +:加,一元取正。

□ -:减,一元取负。

□ *:乘。

□ /:除。

□ %:取模(求余)。

□ --:减1。

□ ++:加1。

Swift语言的运算符中有一元运算符和二元运算符,在本节将详细讲解这两种运算符的基本知识。

4.3.1 一元运算符

一元运算符,指只有一个操作对象。C语言中的单目运算符有++(自增1,操作对象必须为变量)、--(自减1,运算对象必须为变量)、+(取正)、-(取负),共对应4种运算。例如,-a是对a进行一元负操作。

(1)自增和自减运算。

和C语言一样,Swift 也提供了方便对变量本身加1或减1的自增(++)和自减(--)的运算符。其操作对象可以是整型和浮点型。例如,如下所示的演示代码。

    var i = 0
    ++i     // 现在 i = 1

在Swift语言中,每调用一次++i,i的值就会加1。实际上,++i是i = i + 1的简写,而--i是i= i-1的简写。++和--既是前置又是后置运算,++i、i++、--i和i--都是有效的写法。

在Swift语言中,自增和自增运算修改了i后有一个返回值。如果只想修改i的值,那么就可以忽略这个返回值。但如果想使用返回值,就需要留意前置和后置运算的返回值是不同的,具体说明如下所示。

□ 当++前置的时候,先自増再返回。

□ 当++后置的时候,先返回再自增。

例如,如下所示的演示代码。

    var a = 0
    let b = ++a // a 和 b 现在都是 1
    let c = a++ // a 现在 2, 但 c 是 a 自增前的值 1

在上述演示代码中,“let b = ++a”先把a加1,然后再返回a的值,所以a和b都是新值1。而“let c = a++”,是先返回a的值,然后a才加1,所以c得到了a的旧值1,而a加1后变成2。

在现实开发应用中,除非需要使用i++的特性,不然推荐使用++i和--i,因为先修改后返回这样的行为更符合我们的逻辑。一般的算术运算符的结合顺序都是“从左往右”的,但是自增和自减运算符的方向却是“从右向左”的。特别是,当++和--与它们同级的运算符一起运算时,一定要注意它们的运算顺序。如-m++,因为负号-和++是属于同级运算符,所以一定要先计算++,然后再计算取负-。

(2)一元负号。

在Swift语言中,数值的正负号可以使用前缀-(即一元负号)来切换,例如,如下所示的演示代码。

    let three = 3
    let minusThree = -three    // minusThree 等于 -3
    let plusThree = -minusThree  // plusThree 等于 3, 或 "负3"

一元负号(-)写在操作数之前,中间没有空格。

(3)一元正号。

在Swift语言中,一元正号(+)不做任何改变地返回操作数的值。例如,如下所示的演示代码。

    let minusSix = -6
    let alsoMinusSix = +minusSix  // alsoMinusSix 等于 -6

虽然一元“+”做无用功,但使用一元负号来表达负数时,可以使用一元正号来表达正数,如此代码会具有对称美。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var a,b,c :Int  //声明两个整型变量
    a=20
    b = ++a;
    println(b)    //输出结果
    a=20
    b = a++;
    println(b)    //输出结果
    a=20
    b = --a;
    println(b)    //输出结果
    a=20
    b = a--;
    println(b)    //输出结果

本实例执行后的效果如图4-3所示。

图4-3 执行效果

4.3.2 二元运算符

二元运算符是指可以有两个操作数进行操作的运算符,Swift 中所有数值类型都支持如下基本的四则运算,这些都是二元运算符。

□ 加法(+)。

□ 减法(-)。

□ 乘法(*)。

□ 除法(/)。

    1 + 2    // 等于 3
    5 - 3    // 等于 2
    2 * 3    // 等于 6
    10.0 / 2.5 // 等于 4.0

与C语言和 Objective-C 不同的是,Swift 默认不允许在数值运算中出现溢出情况。但是可以使用 Swift 的溢出运算符来达到有目的的溢出(如a &+ b)。

在Swift语言中,加法运算符也可用于String的拼接,例如,如下所示的演示代码。

    "hello, " + "world"  // 等于 "hello, world"

两个Character值或一个String和一个Character值,相加会生成一个新的String值,例如,如下所示的演示代码。

    let dog: Character = "d"
    let cow: Character = "c"
    let dogCow = dog + cow
    // dogCow 现在是 "dc"

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var a = 1 //变量
    a = 10  //给变量赋值
    var b = 2 //变量
    let c = a+b //定义一个常量c,c的值等于变量a和变量b的和
    let d = a-b
    let e = a*b
    let f = a/b
    println(c)
    println(d)
    println(e)
    println(f)

本实例执行后的效果如图4-4所示。

图4-4 执行效果

4.3.3 求余运算

求余运算也叫取模运算,是一种双目运算符。求余运算(a % b)是计算b的多少倍刚好可以容入a,返回多出来的那部分(余数)。假如计算9 % 4,应该先计算出4的多少倍会刚好可以容入9中,如图4-5所示。

图4-5 计算9 % 4

由此可见,计算9 % 4的余数是1,用橙色标出。在Swift中的表达方式是。

    9 % 4   // 等于 1

为了得到a % b的结果,%计算了以下等式,并输出余数作为结果。

    a = (b × 倍数) + 余数

当倍数取最大值的时候,就会刚好可以容入a中。

把9和4代入等式中,我们得1。

    9 = (4 × 2) + 1

同样的方法来计算“-9 % 4”,

    -9 % 4  // 等于 -1

把-9和4代入等式,-2是取到的最大整数。

    -9 = (4 × -2) + -1

余数是-1。

在对负数b求余时,b的符号会被忽略。这意味着 a % b 和 a % -b的结果是相同的。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var a,i,j,k,m :Int
    a=1949
    i=a/1000;             //求该数的千位数字
    j=a%1000/100;           //求该数的百位数字
    k=a%1000%100/10;        //求该数的十位数字
    m=a%1000%100%10;        //求该数的个位数字
    println(i)
    println(j)
    println(k)
    println(m)

本实例执行后的效果如图4-6所示。

图4-6 执行效果

4.3.4 浮点数求余计算

不同于C语言和 Objective-C,在Swift程序中可以对浮点数进行求余。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var j,b :Double
    b=2.5
    j=8%b           //求该数的百位数字
    println(j)

本实例执行后的效果如图4-7所示。

图4-7 执行效果

在上述演示代码中,8除于2.5等于3余0.5,所以结果是一个Double值0.5,如图4-8所示。

图4-8 浮点运算演示

4.4 比较运算符(关系运算符)

在 Swift 语言程序中经常用到关系运算,其实关系运算就是比较运算。所有标准 C 语言中的比较运算都可以在Swift 中使用,具体说明如下所示。

□ 等于(a == b)。

□ 不等于(a != b)。

□ 大于(a > b)。

□ 小于(a < b)。

□ 大于等于(a >= b)。

□ 小于等于(a <= b)。

上述关系运算符的优先级低于算数运算符,高于赋值运算符。其中<、<=、>和>=同级的,而==和!=是同级的,并且前4种的优先级高于后两种。

在 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

在Swift语言中,比较运算多用于条件语句,如if条件。例如,如下所示的演示代码。

    let name = "world"
    if name == "world" {
        println("hello, world")
    } else {
        println("I'm sorry \(name), but I don't recognize you")
    }
    // 输出 "hello, world", 因为"name" 就是等于 "world"

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    let a = 20
    let b=10
    if a>b {
        println("hello, world")
    } else {
        println("I'm sorry \(a), but I don't recognize you")
    }

本实例执行后的效果如图4-9所示。

图4-9 执行效果

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    let a = 20
    let b = 10
    println(a==b)
    println(a>=b)
    println(a<=b)
    println(a<b)
    println(a>b)

本实例执行后的效果如图4-10所示。

图4-10 执行效果

4.5 三元条件运算

在Swift语言中,三元条件运算有3个操作数,其使用原型如下所示。

    问题 ? 答案1 : 答案2

三元条件运算可以简洁地表达根据问题成立与否作出二选一的操作。如果问题成立,返回答案1的结果;如果不成立,则返回答案2的结果。

例如使用三元条件运算简化了如下所示的代码。

    if question: {
      answer1
    } else {
      answer2
    }

下面有一个计算表格行高的例子,如果有表头,那么行高应比内容高度要高出50像素。如果没有表头,只需高出20像素。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    let contentHeight = 40
    let hasHeader = true
    let rowHeight = contentHeight + (hasHeader ? 50 : 20)
    println(contentHeight)
    println(hasHeader)
    println(rowHeight)

本实例执行后的效果如图4-11所示。

图4-11 执行效果

这样写代码会简洁:

    let contentHeight = 40
    let hasHeader = true
    var rowHeight = contentHeight
    if hasHeader {
        rowHeight = rowHeight + 50
    } else {
        rowHeight = rowHeight + 20
    }
    // rowHeight 现在是 90

在上述代码中,第一段代码例子使用了三元条件运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将rowHeight定义成变量,因为它的值无需在if语句中改变。

在Swift语言中,三元条件运算提供了有效率并且便捷的方式来表达二选一的选择。读者需要注意的是,过度使用三元条件运算就会让简洁的代码变成难懂的代码,所以,应该应避免在一个组合语句使用多个三元条件运算符。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    let a = 40
    let b=20
    let c = a>b
    let d = c ? 50 : 20
    println(a)
    println(b)
    println(c)
    println(d)

本实例执行后的效果如图4-12所示。

图4-12 执行效果

4.6 区间运算符

在Swift语言中提供了两个方便表达一个区间的值的运算符,分别是闭区间运算符和半闭区间运算符。在本节的内容中,将详细讲解区间运算符的基本知识。

4.6.1 闭区间运算符

在Swift语言中,闭区间运算符(a...b)定义一个包含从a到b(包括a和b)的所有值的区间。闭区间运算符在迭代一个区间的所有值时是非常有用的,例如,在for-in循环中的如下所示的演示代码。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    for index in 1...5 {
    println("Hello, World!")
    println("\(index) * 5 = \(index * 5)")
    }

本实例执行后的效果如图4-13所示。

图4-13 执行效果

4.6.2 半闭区间运算符

在Swift语言中,半闭区间(a..b)定义了一个从a到b但不包括b的区间。之所以称为半闭区间,是因为该区间包含第一个值而不包括最后的值。

在Swift语言中,半闭区间的实用性在于,当你使用一个以0开始的列表(如数组)时,非常方便地从0数到列表的长度。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    let coutry = ["荷兰", "德国", "巴西", "法国"]
    let count = coutry.count
    for i in 0..count {
        println("2014年世界杯第 \(i + 1) 名: \(coutry[i])")
    }

本实例执行后的效果如图4-14所示。

图4-14 执行效果

在上述代码中的数组有4个元素,但是“0..count”只数到3(最后一个元素的下标),因为它是半闭区间。

4.7 逻辑运算

在Swift语言中,逻辑运算就是将关系表达式用逻辑运算符连接起来,并对其求值的一个运算过程。逻辑运算的操作对象是逻辑布尔值。Swift 支持基于 C 语言的3个标准逻辑运算。

□ 逻辑非(!a)。

□ 逻辑与(a && b)。

□ 逻辑或(a || b)。

其中,“逻辑与”和“逻辑或”是双目运算符,要求有两个运算量,例如 (A>B) && (X>Y)。“逻辑非”是单目运算符,只要求有一个运算量,如!(A>B)。

“?”“逻辑与”相当于我们日常生活中说的“并且”,就是两个条件都成立的情况下“逻辑与”的运算结果才为“真”。“逻辑或”相当于生活中的“或者”,当两个条件中有任一个条件满足,“逻辑或”的运算结果就为“真”。“逻辑非”相当于生活中的“不”,当一个条件为真时,“逻辑非”的运算结果为“假”。

表4-1中所列是a和b之间的逻辑运算,在此假设a=5,b=2。

表4-1 逻辑运算

从表4-2中的运算结果可以得出如下规律。

(1)进行与运算时,只要参与运算中的两个对象有一个是假,则结果就为假。

(2)进行或运算时,只要参与运算中的两个对象有一个是真,则结果就为真。

4.7.1 逻辑非

在Swift语言中,逻辑非运算(!a)对一个布尔值取反,使得true变false,false变true。“!a”是一个前置运算符,需出现在操作数之前,且不加空格,读作非a。例如,如下所示的演示代码。

    let allowedEntry = false
    if !allowedEntry {
        println("ACCESS DENIED")
    }
    // 输出 "ACCESS DENIED"

在上述演示代码中,if !allowedEntry语句可以读作“如果 非 alowed entry。”,接下一行代码只有在如果“非 allow entry”为true,即allowEntry为false时被执行。

4.7.2 逻辑与

在Swift语言中,逻辑与(a && b)表达了只有a和b的值都为true时,整个表达式的值才会是true。只要任意一个值为false,整个表达式的值就为false。事实上,如果第一个值为false,那么是不去计算第二个值的,因为它已经不可能影响整个表达式的结果了。这被称作“短路计算(short-circuit evaluation)”。

例如,如下所示的演示代码,只有两个Bool值都为true值的时候才允许进入。

    let enteredDoorCode = true
    let passedRetinaScan = false
    if enteredDoorCode && passedRetinaScan {
        println("Welcome!")
    } else {
        println("ACCESS DENIED")
    }
    // 输出 "ACCESS DENIED"

4.7.3 逻辑或

在Swift语言中,逻辑或(a || b)是一个由两个连续的“|”组成的中置运算符。逻辑或表示了两个逻辑表达式的其中一个为true,整个表达式就为true。

同逻辑与运算类似,逻辑或也是“短路计算”的,当左端的表达式为true时,将不计算右边的表达式了,因为它不可能改变整个表达式的值了。

例如,如下所示的演示代码,第一个布尔值(hasDoorKey)为 false,但第二个值(knowsOverridePassword)为true,所以整个表达式是true,于是允许进入。

    let hasDoorKey = false
    let knowsOverridePassword = true
    if hasDoorKey || knowsOverridePassword {
        println("Welcome!")
    } else {
        println("ACCESS DENIED")
    }
    // 输出 "Welcome!"

4.7.4 组合逻辑

在Swift语言中,可以组合多个逻辑运算来表达一个复合逻辑。例如,如下所示的演示代码。

    if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
        println("Welcome!")
    } else {
        println("ACCESS DENIED")
    }
    // 输出 "Welcome!"

这个例子使用了含多个“&&”和“||”的复合逻辑。但是无论怎样,“&&”和“||”始终只能操作两个值。所以,这实际是3个简单逻辑连续操作的结果。对上述演示代码的具体说明如下所示。

如果输入了正确的密码并通过了视网膜扫描,或者我们有一把有效的钥匙,又或者我们知道紧急情况下重置的密码,就能把门打开进入。如果对于前两种情况都不能满足,前两个简单逻辑的结果是false,但是知道紧急情况下重置的密码的,所以整个复杂表达式的值还是true。

4.7.5 使用括号设置运算优先级

在Swift语言中,为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,给第一个部分加个括号,使用它看起来逻辑更明确。

    if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
        println("Welcome!")
    } else {
        println("ACCESS DENIED")
    }
    // 输出 "Welcome!"

这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,建议在可以让代码变清晰的地方加一个括号。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    //声明变量并定义初值
    var a,b,c:Int
    a=17
    b=15
    c=20
    let x=a>b
    let y=a<c
    if x && y {
        println("欢迎光临马拉卡纳球场!!!")
    } else {
        println("对不起,您没有球票,禁止入内!")
    }

本实例执行后的效果如图4-15所示。

图4-15 执行效果

4.8 位运算符

位操作符通常在诸如图像处理和创建设备驱动等底层开发中使用,使用它可以单独操作数据结构中原始数据的比特位。在使用一个自定义的协议进行通信的时候,运用位运算符来对原始数据进行编码和解码也是非常有效的。

在Swift语言中提供了如下6种位运算符。

□ &:按位与。

□ |:按位或。

□ ^:按位异或。

□ ~:取反。

□ <<:左移。

□ >>:右移。

在本节的内容中,将详细讲解Swift位运算符的基本知识。

4.8.1 按位取反运算符

按位取反运算符“~”对一个操作数的每一位都取反,具体过程如图4-16所示。

图4-16 按位取反运算符

“~”运算符是前置的,所以请不加任何空格地写在操作数之前。

    let initialBits: UInt8 = 0b00001111
    let invertedBits = ~initialBits  // 等于 0b11110000

UInt8 是 8 位无符整型,可以存储 0~255 的任意数。这个例子初始化一个整型为二进制值00001111(前4位为0,后4位为1),它的十进制值为15。

使用按位取反运算“~”对initialBits操作,然后赋值给invertedBits这个新常量。这个新常量的值等于所有位都取反的initialBits,即1变成0,0变成1,变成了11110000,十进制值为240。

由此可见,求反运算符“~”为单目运算符,具有向右结合性的特点。其功能是对参与运算的数的各二进位按位求反。例如,~9的运算为。

    ~(0000000000001001)

结果为:

    1414141414140140

4.8.2 按位与运算符

按位与运算符“&”是一个双目运算符,其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1,否则为0。参与运算的数以补码方式出现。例如,9&5可以写为如下所示的算式。

可由此见9&5=1。

按位与运算通常用来对某些位清0或保留某些位。例如,把a的高八位清 0,保留低八位,可作a&255运算(255的二进制数为0000000014141414)。

位与运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑与运算。例如,int型常量4和7进行位与运算的过程如下所示。

如果是对于负数,需要按其补码进行运算。例如int型常量-4和7进行位与运算的运算过程如下所示。

位与运算的主要用途如下所示。

(1)清零:快速对某一段数据单元的数据清零,即将其全部的二进制位为0。例如,整型数a=321对其全部数据清零的操作为a=a&0x0。

(2)获取一个数据的指定位。

(3)保留数据区的特定位。假如要获得整型数a=的第7~8位(从0开始)位的数据操作为,则可以通过如下所示的过程实现。

按位与运算符“&”对两个数进行操作,然后返回一个新的数,这个数的每个位都需要两个输入数的同一位都为1时才为1。具体过程如图4-17所示。

图4-17 按位与运算符

以下代码,firstSixBits和lastSixBits中间4个位都为1。对它俩进行按位与运算后,就得到了00111100,即十进制的60。

    let firstSixBits: UInt8 = 0b11111100
    let lastSixBits: UInt8  = 0b00111111
    let middleFourBits = firstSixBits & lastSixBits  // 等于 00111100

4.8.3 按位或运算符

按位或运算符“|”的功能是比较两个数,然后返回一个新的数,这个数的每一位设置 1 的条件是两个输入数的同一位都不为0(即任意一个为1,或都为1)。具体过程如图4-18所示。

图4-18 按位或运算

按位或运算符“|”是一个双目运算符,其功能是参与运算的两数各对应的二进位相或。只要对应的两个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。例如,int型常量5和7进行位或运算的表达式为5|7,具体结果如下所示。

位或运算的主要用途是设定一个数据的指定位。如整型数“a=321”,将其低 8 位数据置为 1的操作为“a=a|0XFF”。

例如:9|5的可写算式如下所示。

例如在下面的演示代码中,someBits 和 moreBits 在不同位上有 1。按位或运行的结果是11111110,即十进制的254。

    let someBits: UInt8 = 0b10110010
    let moreBits: UInt8 = 0b01011110
    let combinedbits = someBits | moreBits  // 等于 11111110

4.8.4 按位异或运算符

按位异或运算符“^”的功能是比较两个数,然后返回一个数,这个数的每个位设为1的条件是两个输入数的同一位不同,如果相同就设为0。具体过程如图4-19所示。

图4-19 按位异或运算符

按位异或运算符“^”是双目运算符,其功能是参与运算的两数各对应的二进位相异或。当两对应的二进位相异时,结果为1。参与运算数仍以补码出现,如9^5可写成如下所示的算式。

例如,int型常量5和7进行位异或运算的表达式为5^7,结果如下所示。

位异或运算的主要用途如下所示。

(1)定位翻转。例如设定一个数据的指定位,将1换为0,0换为1。如整型数“a=321”,将其低8位数据进行翻位的操作为a=a^0XFF,即下面的运算过程。

(2)数值交换。例如 a=3、b=4,这样可以通过如下代码,无须引入第三个变量,利用位运算即可实现数据交换。

例如在下面的演示代码中,firstBits和otherBits都有一个1与另一个数不同。所以按位异或的结果是把它这些位置为1,其他都置为0。

    let firstBits: UInt8 = 0b00010100
    let otherBits: UInt8 = 0b00000101
    let outputBits = firstBits ^ otherBits  // 等于 00010001

4.8.5 按位左移/右移运算符

左移运算符“<<”和右移运算符“>>”会把一个数的所有比特位按以下定义的规则向左或向右移动指定位数。按位左移和按位右移的效果相当于把一个整数乘于或除于一个因子为2的整数。向左移动一个整型的比特位相当于把这个数乘于2,向右移一位就是除于2。

1.左移运算符

左移运算符“<<”是双目运算符,其功能把“<<”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。例如:

    a<<4

上述代码的功能是把 a 的各二进位向左移动 4 位。如 a=00000014(十进制 3),左移 4 位后为00140000(十进制48)。

左移运算的实质是将对应的数据的二进制值逐位左移若干位,并在空出的位置上填0,最高位溢出并舍弃。

右移运算符“>>”是一个双目运算符,其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。例如:

    a=15,
    a>>2

这表示把000001414右移为00000014(十进制3)。

2.右移运算符

右移运算符的实质是将对应的数据的二进制值逐位右移若干位,并舍弃出界的数字。如果当前的数为无符号数,高位补零。

如果当前的数据为有符号数,在进行右移的时候,根据符号位决定左边补0还是补1。如果符号位为0,则左边补0;但是如果符号位为1,则根据不同的计算机系统,可能有不同的处理方式。

由此可以看出,位右移运算可以实现对除数为2的整除运算。在此需要说明的是,对于有符号数,在右移时符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位为1,最高位是补0或是补1取决于编译系统的规定。

3.无符整型的移位操作

对无符整型的移位的效果如下。

已经存在的比特位向左或向右移动指定的位数。被移出整型存储边界的位数直接抛弃,移动留下的空白位用零0来填充。这种方法称为逻辑移位。

以下展示了11111111 << 1(11111111向左移1位)和11111111 >> 1(11111111向右移1位)。蓝色的是被移位的,灰色是被抛弃的,橙色的0是被填充进来的。具体过程如图4-20所示。

图4-20 无符整型的移位操作过程

例如,如下所示的演示代码。

    let shiftBits: UInt8 = 4  // 即二进制的00000100
    shiftBits << 1        // 00001000
    shiftBits << 2        // 00010000
    shiftBits << 5        // 10000000
    shiftBits << 6        // 00000000
    shiftBits >> 2        // 00000001

可以使用移位操作进行其他数据类型的编码和解码。

    let pink: UInt32 = 0xCC6699
    let redComponent = (pink & 0xFF0000) >> 16   // redComponent 是 0xCC, 即 204
    let greenComponent = (pink & 0x00FF00) >> 8  // greenComponent 是 0x66, 即 102
    let blueComponent = pink & 0x0000FF        // blueComponent 是 0x99, 即 153

上述演示代码使用了一个 UInt32 的命名为 pink 的常量来存储层叠样式表 CSS 中粉色的颜色值,CSS颜色#CC6699在Swift用十六进制0xCC6699来表示。然后使用按位与(&)和按位右移就可以从这个颜色值中解析出红(CC)、绿(66)、蓝(99)3个部分。

对 0xCC6699 和 0xFF0000 进行按位与&操作就可以得到红色部分。0xFF0000 中的 0 遮盖了OxCC6699的第二和第三个字节,这样6699被忽略了,只留下0xCC0000。

然后,按向右移动16位,即 >> 16。十六进制中每两个字符是8比特位,所以移动16位的结果是把0xCC0000变成0x0000CC。这和0xCC是相等的,都是十进制的204。

同样的,绿色部分来自于0xCC6699和0x00FF00的按位操作得到0x006600。然后向右移动8位,得到0x66,即十进制的102。

最后,蓝色部分对0xCC6699和0x0000FF进行按位与运算,得到0x000099,无需向右移位了,所以结果就是0x99,即十进制的153。

4.有符整型的移位操作

有符整型的移位操作相对复杂得多,因为正负号也是用二进制位表示的。有符整型通过第 1个比特位(称为符号位)来表达这个整数是正数还是负数。0代表正数,1代表负数。其余的比特位(称为数值位)存储其实值。有符正整数和无符正整数在计算机里的存储结果是一样的,下面来看“+4”内部的二进制结构,具体如图4-21所示。

图4-21 “+4”内部的二进制结构

符号位为0,代表正数,另外7比特位二进制表示的实际值就刚好是4。负数与正数不同,负数存储的是2的n次方减去它的绝对值,n为数值位的位数。一个8比特的数有7个数值位,所以是2的7次方,即128。

接下来看“-4”存储的二进制结构,具体如图4-22所示。

图4-22 -4存储的二进制结构

现在符号位为1,代表负数,7个数值位要表达的二进制值是124,即128 – 4,具体如图4-23所示。

图4-23 7个数值位要表达的二进制值是124

负数的编码方式称为二进制补码表示。这种表示方式看起来很奇怪,但它有几个优点。

第一,只需要对全部8个比特位(包括符号)做标准的二进制加法就可以完成-1 + -4的操作,忽略加法过程产生的超过8个比特位表达的任何信息,具体过程如图4-24所示。

图4-24 完成-1+-4的操作

第二,由于使用二进制补码表示,我们可以和正数一样对负数进行按位左移或右移,同样也是左移1位时乘于2,右移1位时除于2。要达到此目的,对有符整型的右移有一个特别的要求。

对有符整型按位右移时,使用符号位(正数为0,负数为1)填充空白位,具体过程如图4-25所示。

图4-25 对有符整型按位右移

这就确保了在右移的过程中,有符整型的符号不会发生变化。这称为算术移位。正因为正数和负数特殊的存储方式,向右移位使它接近于0。移位过程中保持符号不变,负数在接近0的过程中一直是负数。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var a,b,i:Int//定义3个整型变量
    a=255
    b=10
    //计算两个数的与运算
    println(a & b);
    //计算两个数的或运算
    println(a | b);
    //计算两个数的异或运算
    println(a^b);
    //计算a进行取反运算的值
    println(~a);
    for(i=1;i<4;i++)
    {
          b=a<<i              //使a左移i位
          println(b)    //输出当前左移结果
    }
        for(i=1;i<4;i++)
    {
          b=a>>i;        //使a右移i位
          println(b)       //输出当前右移结果
    }

本实例执行后的效果如图4-26所示。

图4-26 执行效果

4.9 溢出运算符

在默认情况下,当往一个整型常量或变量赋于一个它不能承载的大数时,Swift 会发出报错信息。这样,在操作过大或过小的数的时候就很安全了。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var mm:Int16
    mm = 12
    println(mm)
    var nn = Int16.max
    // nn 等于 32767, 这是 Int16 能承载的最大整数
    nn += 1//出错
    println(nn)

本实例执行后的效果如图4-27所示。

图4-27 执行效果

并且Xcode 6会报错,如图4-28所示。

图4-28 报错信息

例如,Int16整型能承载的整数范围是-32768~32767,如果给它赋上超过这个范围的数时就会报错。

    var potentialOverflow = Int16.max
    // potentialOverflow 等于 32767, 这是 Int16 能承载的最大整数
    potentialOverflow += 1
    //出错

对过大或过小的数值进行错误处理让你的数值边界条件更灵活。

当然,如果有意在溢出时对有效位进行截断,可以采用溢出运算,而不说错误处理。在 Swfit语言中,为整型计算提供了如下5个“&”符号开头的溢出运算符。

□ 溢出加法 &+。

□ 溢出减法 &-。

□ 溢出乘法 &*。

□ 溢出除法 &/。

□ 溢出求余 &%。

(1)值的上溢出。

下面例子使用了溢出加法“&+”来解剖的无符整数的上溢出。

    var willOverflow = UInt8.max
    // willOverflow 等于UInt8的最大整数 255
    willOverflow = willOverflow &+ 1
    // 这时候 willOverflow 等于 0

在上述演示代码中,willOverflow 用 Int8 所能承载的最大值 255(二进制 11111111),然后用&+加 1。然后 UInt8 就无法表达这个新值的二进制了,也就导致了这个新值上溢出了,如图 4-29所示。溢出后,新值在UInt8的承载范围内的那部分是00000000,也就是0。

图4-29 导致新值溢出

(2)值的下溢出。

数值也有可能因为太小而越界。举个例子,UInt8的最小值是0(二进制为00000000)。使用“&-”进行溢出减1,就会得到二进制的11111111,即十进制的255,具体过程如图4-30所示。

图4-30 值的下溢出

例如,如下所示的对应的Swift代码。

    var willUnderflow = UInt8.min
    // willUnderflow 等于UInt8的最小值0
    willUnderflow = willUnderflow &- 1
    // 此时 willUnderflow 等于 255

有符整型也有类似的下溢出,有符整型所有的减法也都是对包括在符号位在内的二进制数进行二进制减法,最小的有符整数是-128,即二进制的10000000。用溢出减法减去1后,变成了01111111,即UInt8所能承载的最大整数127,具体过程如图4-31所示。

图4-31 有符整型的下溢出

例如,如下所示的对应的Swift代码。

    var signedUnderflow = Int8.min
    // signedUnderflow 等于最小的有符整数 -128
    signedUnderflow = signedUnderflow &- 1
    // 如今 signedUnderflow 等于 127

(3)除零溢出。

一个数除于0 i / 0,或者对0求余数 i % 0,就会产生一个错误。

    let x = 1
    let y = x / 0

当使用它们对应的可溢出的版本的运算符“&/”和“&%”进行除0操作时,就会得到0值。

    let x = 1
    let y = x &/ 0
    // y 等于 0

4.10 运算符函数

在Swift语言中,让已有的运算符也可以对自定义的类和结构进行运算,这称为运算符重载。在本节的演示代码中,展示了如何用“+”让一个自定义的结构做加法。算术运算符“+”是一个两目运算符,因为它有两个操作数,而且它必须出现在两个操作数之间。在演示代码中定义了一个名为Vector2D的二维坐标向量(x,y)的结构,然后定义了让两个Vector2D的对象相加的运算符函数。

    struct Vector2D {
        var x = 0.0, y = 0.0
    }
    @infix func + (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }

该运算符函数定义了一个全局的“+”函数,这个函数需要两个Vector2D类型的参数,返回值也是Vector2D类型。需要定义和实现一个中置运算的时候,在关键字func之前写上属性 @infix 即可。在上述演示代码中,参数被命名为了left和right,代表“+”左边和右边的两个Vector2D对象。函数返回了一个新的Vector2D的对象,这个对象的x和y分别等于两个参数对象的x和y的和。上述函数是全局的,而不是Vector2D结构的成员方法,所以,任意两个Vector2D对象都可以使用这个中置运算符。

    let vector = Vector2D(x: 3.0, y: 1.0)
    let anotherVector = Vector2D(x: 2.0, y: 4.0)
    let combinedVector = vector + anotherVector
    // combinedVector 是一个新的Vector2D, 值为 (5.0, 5.0)

上述演示代码实现了两个向量(3.0,1.0)和(2.0,4.0)相加,得到向量(5.0,5.0)的过程。具体过程如图4-32所示。

4.10.1 前置和后置运算符

本节前面的演示实例演示了一个双目中置运算符的自定义实现,同样也可以通过标准单目运算符的实现。单目运算符只有一个操作数,在操作数之前就是前置的,如“-a”在操作数之后就是后置的,例如i++。

图4-32 向量(3.0,1.0)和(2.0,4.0)相加

在Swift语言中,在实现一个前置或后置运算符时,在定义该运算符的时候于关键字func之前标注 @prefix 或 @postfix 属性。

    @prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }

上述代码为Vector2D类型提供了单目减运算“-a”,属性@prefix表明这是个前置运算符。

对于数值来说,单目减运算符可以把正数变负数、把负数变正数。对于Vector2D,单目减运算将其x和y都进行单目减运算。

    let positive = Vector2D(x: 3.0, y: 4.0)
    let negative = -positive
    // negative 为 (-3.0, -4.0)
    let alsoPositive = -negative
    // alsoPositive 为 (3.0, 4.0)

4.10.2 组合赋值运算符

在Swift语言中,组合赋值是其他运算符和赋值运算符一起执行的运算。如“+=”把加运算和赋值运算组合成一个操作。实现一个组合赋值符号需要使用@assignment 属性,还需要把运算符的左参数设置成 inout,因为这个参数会在运算符函数内直接修改它的值。例如,如下所示的演示代码。

    @assignment func += (inout left: Vector2D, right: Vector2D) {
        left = left + right
    }

因为加法运算在之前定义过了,所以在此无需重新定义。加赋运算符函数使用已经存在的高级加法运算符函数来执行左值加右值的运算。例如,如下所示的演示代码。

    var original = Vector2D(x: 1.0, y: 2.0)
    let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
    original += vectorToAdd
    // original 现在为 (4.0, 6.0)

可以将 @assignment 属性和 @prefix 或 @postfix 属性一起来组合,实现一个Vector2D的前置运算符。例如,如下所示的演示代码。

    @prefix @assignment func ++ (inout vector: Vector2D) -> Vector2D {
        vector += Vector2D(x: 1.0, y: 1.0)
        return vector
    }

这个前置使用了已经定义好的高级加赋运算,将自己加上一个值为 (1.0,1.0) 的对象,然后赋给自己,然后再将自己返回。例如,如下所示的演示代码。

    var toIncrement = Vector2D(x: 3.0, y: 4.0)
    let afterIncrement = ++toIncrement
    // toIncrement 现在是 (4.0, 5.0)
    // afterIncrement 现在也是 (4.0, 5.0)

注意

默认的赋值符是不可重载的。只有组合赋值符可以重载。三目条件运算符a?b:c也是不可重载。

4.10.3 比较运算符

在Swift语言中,无法知道自定义类型是否相等或不等,因为等于或者不等于由你的代码说了算。所以自定义的类和结构要使用比较符“==”或“!=”就需要重载。

在Swift语言中,定义相等运算符函数与定义其他中置运算符相同,例如,如下所示的演示代码。

    @infix func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
    @infix func != (left: Vector2D, right: Vector2D) -> Bool {
        return !(left == right)
    }

上述代码实现了相等运算符“==”来判断两个Vector2D对象是否有相等的值,相等的概念就是它们有相同的 x 值和相同的 y 值,我们就用这个逻辑来实现。接着使用“==”的结果实现了不相等运算符“!=”。

现在可以使用这两个运算符来判断两个Vector2D对象是否相等,例如,如下所示的演示代码。

    let twoThree = Vector2D(x: 2.0, y: 3.0)
    let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
    if twoThree == anotherTwoThree {
        println("这两个向量是相等的.")
    }
    // prints "这两个向量是相等的."

4.11 自定义运算符

在开发Swift程序的过程中,如果觉得内置的运算符不够用,那么可以声明一些个性的运算符,但个性的运算符只能使用这些字符 / = - + * % < >!& | ^。~。

在Swift语言中,新的运算符声明需在全局域使用operator关键字声明,可以声明为前置、中置或后置的。

    operator prefix +++ {}

上述演示代码定义了一个新的前置运算符叫“+++”,此前Swift并不存在这个运算符。此处为了演示,我们让“+++”对Vector2D对象的操作定义为双自增这样一个独有的操作,这个操作使用了之前定义的加赋运算,实现了自己加上自己然后返回的运算。

    @prefix @assignment func +++ (inout vector: Vector2D) -> Vector2D {
        vector += vector
        return vector
    }

Vector2D 的“+++”的实现和“++”的实现很相似,唯一不同的前者是加自己,后者是加值为(1.0, 1.0)的向量。

    var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
    let afterDoubling = +++toBeDoubled
    // toBeDoubled 现在是 (2.0, 8.0)
    // afterDoubling 现在也是 (2.0, 8.0)

4.12 运算符的优先级和结合性

优先级,即处理的先后顺序。在日常生活中,无论是排队买票还是超市结账,我们都遵循先来后到的顺序。在Swift语言运算中,也要遵循某种运算秩序。Swift语言运算符的运算优先级共分为15 级,1 级最高,15 级最低。在表达式中,优先级较高的先于优先级较低的进行运算。当一个运算符号两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理。

如果属于同级运算符,则按照运算符的结合性方向来处理。Swift 语言中各运算符的结合性可以分为如下两种。

□ 左结合性:自左至右进行运算。

□ 右结合性:自右至左进行运算。

例如,算术运算符的结合性是自左至右,即先左后右。如有表达式 x-y+z,则 y 应先与“-”号结合,执行x-y运算,然后再执行+z的运算。这种自左至右的结合方向就称为“左结合性”。而自右至左的结合方向称为“右结合性”。最典型的右结合性运算符是赋值运算符。如 x=y=z,由于“=”的右结合性,应先执行y=z,再执行x=(y=z)运算。

Swift语言运算符中有不少为右结合性,应注意区别,以避免理解错误。

Swift语言运算符优先级的具体说明如表4-2所示。

表4-2 Swift语言运算符优先级

在Swift语言中,可以为自定义的中置运算符指定优先级和结合性。可以回头看看优先级和结合性,解释这两个因素是如何影响多种中置运算符混合表达式的计算的。

在Swift语言中,结合性(associativity)的值可取的值有left、right和none。左结合运算符与其他优先级相同的左结合运算符写在一起时,会与左边的操作数结合。同理,右结合运算符会与右边的操作数结合。而非结合运算符不能与其他相同优先级的运算符写在一起。

结合性(associativity)的值默认为none,优先级(recedence)默认为100。例如,在下面的演示代码定义了一个新的中置符“+-”,是左结合的left,优先级为140。

    operator infix +- { associativity left precedence 140 }
    func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
    let firstVector = Vector2D(x: 1.0, y: 2.0)
    let secondVector = Vector2D(x: 3.0, y: 4.0)
    let plusMinusVector = firstVector +- secondVector
    // plusMinusVector 此时的值为 (4.0, -2.0)

上述运算符把两个向量的x相加,把向量的y相减。因为它实际是属于加减运算,所以让它保持了和加法一样的结合性和优先级(left和140)。

在Swift语言中,运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。结合性定义相同优先级的运算符在一起时是怎么组合或关联的,是和左边的一组呢,还是和右边的一组。意思就是,到底是和左边的表达式结合呢,还是和右边的表达式结合?

在混合表达式中,运算符的优先级和结合性是非常重要的。举个例子,为什么下列表达式的结果为4?

    2 + 3 * 4 % 5
    // 结果是 4

如果严格地从左计算到右,计算过程会是这样。

    2 + 3 = 5
    5 * 4 = 20
    20 / 5 = 4 余 0

但是正确的结果是4而不是0。优先级高的运算符要先计算,在Swift和C语言中,都是先乘除后加减的。所以,执行完乘法和求余运算才能执行加减运算。乘法和求余拥有相同的优先级,在运算过程中,我们还需要结合性,乘法和求余运算都是左结合的。这相当于在表达式中有隐藏的括号让运算从左开始。2 + ((3 * 4) % 5)的计算过程如下。

首先计算3 * 4 = 12,所以这相当于。

    2 + (12 % 5)

因为12 % 5 = 2,所这这相当于。

    2 + 2

所以最终的计算结果为4。

注意

Swift的运算符较C语言和Objective-C更简单和保守,这意味着与基于C的语言可能不一样。所以,在移植已有代码到Swift时,需要确保代码按我们想的那样去执行。

实例文件main.swift的具体实现代码如下所示。

    import Cocoa
    var str = "Hello, playground"
    /**
    注意:在swift中x=y是不返回任何东西的,所以如果
    if x=y{
    }这样子去判断的话会出现错误
    */
    var x =  2, y = 4
    if x == y{
    }
    var addStr = str + "!你好,雨燕"
    /**
    和c、Objective-c相比较,Swift支持浮点型余数
    */
    var remainderOfFloat = 8%2.5
    /**
    快速遍历中a...b代表 从a到b并包括b ,  a..b 代表从a到b不包括b
    a...b
    a..b
    */
    for index in 1...5 {
        println("\(index) times 5 is \(index * 5)")
    }
    let names = ["Anna", "Alex", "Brian", "Jack"]
    let count = names.count
    for i in 0..count {
        println("Person \(i + 1) is called \(names[i])")
    }

本实例执行后的效果如图4-33所示。

图4-33 执行效果