讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 2.1 函數參數與返回值 >

2.1 函數參數與返回值

函數就像是小學數學課本中所講的那種能夠處理各種東西的機器一樣。你知道我說的是什麼:上面是一個漏斗,中間是一些齒輪和曲柄,下面的管子就會出來東西。函數就像這樣的機器一樣:你提供一些東西,然後由特定的機器進行處理,最後東西就生成出來了。

提供的東西是輸入,出來的東西是輸出。從技術的角度來看,函數的輸入叫作參數,輸出叫作結果。比如,下面這個簡單的函數有兩個Int值輸入,然後將它們相加,最後輸出相加的和:


func sum (x:Int, _ y:Int) -> Int {
    let result = x + y
    return result
}  

這裡所用的語法是非常嚴格且定義良好的,如果理解不好你就沒法用好Swift。下面來詳細介紹;我將第1行分為幾塊來分別介紹:


func sum 1
    (x:Int, _ y:Int) 23
    -> Int { 4
        let result = x + y 5
        return result 6
}  

1聲明從關鍵字func開始,後跟函數的名字,這裡就是sum。調用函數時必須使用這個名字,所謂調用就是運行函數所包含的代碼。

2函數名後面跟著的是其參數列表,至少要包含一對圓括號。如果函數接收參數(輸入),那麼它們就會列在圓括號中,中間用逗號隔開。每個參數都有嚴格的格式:參數名,一個冒號,然後是參數類型。這裡的sum函數接收兩個參數:一個是Int,其名字為x;另一個也是Int,其名字為y。

值得注意的是,名字x與y是隨意起的,它們只對該函數起作用。它們與其他函數或是更高層次作用域中所使用的其他x與y是不同的。定義這些名字的目的在於讓參數值能有自己的名字,這樣就可以在函數體的代碼塊中引用它們了。實際上,參數聲明是一種變量聲明:我們聲明變量x與y以便在該函數中使用它們。

3該函數聲明的參數列表中第2個參數名前還有一個下劃線(_)和一個空格。現在我不打算介紹這個下劃線,只是這個示例需要用到它,因此相信我就行了。

4圓括號之後是一個箭頭運算符→,後跟函數所返回的值類型。接下來是一對花括號,包圍了函數體——實際代碼。

5在花括號中(即函數體),作為參數名定義的變量進入生命週期,其類型是在參數列表中指定的。我們知道,只有當函數被調用並將值作為參數傳遞進去時,這些代碼才會運行。

這裡的參數叫作x與y,這樣我們就可以安全地使用這些值,通過其名字來引用它們,我們知道這些值都會存在,並且是Int值,這是通過參數列表來指定的。不僅是程序員,編譯器也可以確保這一點。

6如果函數有返回值,那麼它必須要使用關鍵字return,後跟要返回的值。該值的類型要與之前聲明的返回值類型(位於箭頭運算符之後)相匹配。

這裡返回了一個名為result的變量;它是通過將兩個Int值相加創建出來的,因此也是個Int,這正是該函數所生成的結果。如果嘗試返回一個String(return\"howdy\")或是省略掉return語句,那麼編譯器就會報錯。

關鍵字return實際上做了兩件事。它返回後面的值,同時又終止了函數的執行。return語句後面可以跟著多行代碼,不過如果這些代碼行永遠都不會執行,那麼編譯器就會發出警告。

花括號之前的函數聲明是一種契約,描述了什麼樣的值可以作為輸入,產生的輸出會是什麼樣子。根據該契約,函數可以接收一定量的參數,每個參數都有確定的類型,並會生成確定類型的結果。一切都要遵循這個契約。花括號中的函數體可以將參數作為局部變量。返回值要與聲明的返回類型相匹配。

這個契約會應用到調用該函數的任何地方。如下代碼調用了sum函數:


let z = sum(4,5)  

重點關注等號右側——sum(4,5),這是個函數調用。它是如何構建的呢?它使用了函數的名字、名字後跟一對圓括號;在圓括號裡面是逗號分隔的傳遞給函數參數的值。從技術角度來說,這些值叫作實參。我這裡使用了Int字面值,不過還可以使用Int變量;唯一的要求就是它要有正確的類型:


let x = 4
let y = 5
let z = sum(y,x)  

在上述代碼中,我故意使用了名字x與y,這兩個變量值會作為參數傳遞給函數,而且還在調用中將它們的順序有意弄反,從而強調這些名字與函數參數列表及函數體中的名字x與y沒有任何關係。對於函數來說,名字並不重要,值才重要,它們的值叫作實參。

函數的返回值呢?該值會在函數調用發生時替換掉函數調用。上述代碼就是這樣的,其結果是9。因此,最後一行就像我說過的:


let z = 9

程序員與編譯器都知道該函數會返回什麼類型的值,還知道在什麼地方調用該函數是合法的,什麼地方調用是不合法的。作為變量z聲明的初始化來調用該函數是沒問題的,這相當於將9作為聲明的初始化部分:在這兩種情況下,結果都是Int,因此z也會聲明為Int。不過像下面這樣寫就不合法了:


let z = sum(4,5) + \"howdy\" // compile error  

由於sum返回一個Int,這相當於將Int加到一個String上。在默認情況下,Swift中不允許這麼做。

忽略函數調用的返回值也是可以的:


sum(4,5)  

上述代碼是合法的;它既不會導致編譯錯誤,也不會造成運行錯誤。這麼做有點無聊,因為我們歷盡辛苦使得sum函數能夠實現4與5相加的結果,但卻沒有用到這個結果,而是將其丟棄掉了。不過,很多時候我們都會忽略掉函數調用的返回值;特別是除了返回值之外,函數還會做一些其他事情(從技術上來說叫作副作用),而調用函數的目的只是讓函數能夠做一些其他事情。

如果在使用Int的地方調用sum,而且sum的參數都是Int值,那是不是就表示你可以在sum調用中再調用sum呢?當然了!這麼做是完全合法的,也是合情合理的:


let z = sum(4,sum(5,6))  

這樣寫代碼存在著一個爭議,那就是你可能被自己搞暈,而且會導致調試變得困難。不過從技術上來說,這麼做是正常的。

2.1.1  Void返回類型與參數

下面回到函數聲明。關於函數參數與返回類型,存在以下兩種情況能夠讓我們更加簡潔地表示函數聲明。

無返回類型的函數

沒有規定說函數必須要有返回值。函數聲明可以沒有返回值。在這種情況下,有3種聲明方式:可以返回Void,可以返回(),還可以完全省略箭頭運算符與返回類型。如下聲明都是合法的:


func say1(s:String) -> Void { print(s) }
func say2(s:String) ->  { print(s) }
func say3(s:String) { print(s) }  

如果函數沒有返回值,那麼函數體就無須包含return語句。如果包含了return語句,那麼其目的就純粹是在該處終止函數的執行。

return語句通常只會包含return。不過,Void(無返回值的函數的返回類型)是Swift中的一個類型。從技術上來說,無返回值的函數實際上返回的就是該類型的值,可以表示為字面值()。(第3章將會介紹字面值()的含義。)因此,函數聲明return()是合法的;無論是否聲明,()就是函數所返回的。寫成return()或return;

(後面加上一個分號)有助於消除歧義,否則Swift可能認為函數會返回下一行的內容。

如果函數無返回值,那麼調用它純粹就是為了函數的副作用;其調用結果無法作為更大的表達式的一部分,這樣函數中代碼的執行就是函數要做的唯一一件事,返回的()值會被忽略。不過,也可以將捕獲的值賦給Void類型的變量;比如:


let pointless : Void = say1(\"howdy\")  

無參數的函數

沒有規定說函數必須要有參數。如果沒有參數,那麼函數聲明中的參數列表就可以完全為空。不過,省略參數列表圓括號本身是不可以的!圓括號需要出現在函數聲明中,位於函數名之後:


func greet1 -> String { return \"howdy\" }  

顯然,函數可以既沒有返回值,也沒有參數;如下代碼的含義是一樣的:


func greet1 -> Void { print(\"howdy\") }
func greet2 ->  { print(\"howdy\") }
func greet3 { print(\"howdy\") }  

就像不能省略函數聲明中的圓括號(參數列表)一樣,你也不能省略函數調用中的圓括號。如果函數沒有參數,那麼這些圓括號就是空的,但必須要有。比如:


greet1  

注意上述代碼中的圓括號!

2.1.2 函數簽名

如果省略函數聲明中的參數名,那就完全可以通過輸入與輸出的類型來描述一個函數了,比如,下面這個表達式:


(Int, Int) -> Int  

事實上,這在Swift中是合法的表達式。它是函數簽名。在該示例中,它表示的是sum函數的簽名。當然,還可能有其他函數也接收兩個Int參數並返回一個Int,這就是要點。該簽名描述了所有接收這些類型、具有這些數量的參數,並返回該類型結果的函數。實際上,函數的簽名是其類型——函數的類型。稍後將會看到,函數擁有類型是非常重要的。

函數的簽名必須要包含參數列表(無參數名)與返回類型,即便其中一樣或兩者都為空亦如此;因此,不接收參數且無返回值的函數簽名可以有4種等價的表示方式,包括Void->Void與()->()。