天天看點

Swift4學習筆記6——函數(Function)

Swift的函數和C的函數定義方式有些差別,它是将傳回類型寫在函數的最後。一般定義函數的文法如下

func函數名 (參數清單) -> 傳回值 {
       //函數體
}
           

舉一個例子,這個函數輸入一個字元串,然後列印出這個字元串,并且傳回一個字元串。

//函數定義
func printYourName (name: String)->String {
    print(name)
    return "Hello, " + name
}

var s = printYourName(name: "Tom")   //函數調用
print(s)    //列印 Hello, Tom
           

如果一個函數沒有傳回值,那麼從 -> 傳回值 這個部分可以省略。比如下面例子

//函數定義
func printYourName (name: String) {
    print(name)
}
           

再來看看有多個參數的情況,比如下面例子。

func printTwoString(firstString: String, secondString: String) {
    print(firstString,secondString)
}
printTwoString(firstString: "hello", secondString: "Kate")
           

Function Argument Labels and Parameter Names

這部分相對Swift2有改變。
           

先了解一個概念,在函數定義時候,參數清單中使用的fristString和secondString稱為參數(Parameter).但是參數包含了兩個東西:

1是Argument Label,它是在方法調用的時候寫在參數值前面的參數标記,比如下面調用中的firstString和secondString。

2是Parameter Names,它隻的是在方法體裡面使用到的參數标志。如printTwoString方法體裡面print種使用到的firstString,secondString。

預設情況下,Argument Label和Parameter Names是一樣的。但是你也可以自定義Argument Label,方法是在Parameter Names前面加上另外一個字元串,并用空格相隔,如下,begin是自定義的Argument Label,然後調用的時候就需要使用begin來指定參數。

func printTwoString(begin firstString: String, secondString: String) {
    print(firstString,secondString)
}
printTwoString(begin: "hello", secondString: "Kate") 
           

你可能注意到了使用print的時候沒有加上任何的Argument Label。如果你不想要Argument Label,那麼在定義方法的時候,将Argument Label的字元串寫為下劃線 _ 。

func printTwoString(firstString: String, _ secondString: String) {
    print(firstString,secondString)
}
printTwoString(firstString: "hello",  "Kate")  //忽略了第二參數的外部參數名之後,這裡就不能加上外部參數名了
           

參數預設值

此外,還可以給參數指派預設值。具有預設值的參數,在調用的時候,可以不用給它指派。好比print方法,它的原型是

但是一般使用的時候都隻傳了一個字元串,原因就在于它後面的兩個參數都是具有預設值的。給參數設定預設值的方法是在方法定義的時候,在參數的後面用 = 加上預設值。如下代碼。

官方文檔建議我們把帶預設值的參數放在參數清單的末尾,這樣在調用的時候不至于混淆。但是其實可以對每個參數都指派預設值。比如下面的例子。

func printTwoString(firstString: String = "hello", secondString: String = "Lucy", thirdString: String = "end") {
    print(firstString,secondString,thirdString)
}
printTwoString( secondString:"two")  //使用外部參數名指定要指派的參數,其他參數使用預設值,輸出 hello two end
           

值得注意的是,如果沒有預設值的參數在調用的時候也沒有給其指派,那麼會在編譯的時候報錯。

如果你又把參數清單的Argument Label都去掉的話,那麼在調用的時候,你給的參數将會從頭開始比對。如果參數類型不比對的話,就會報錯。當然,不建議大家這樣做,因為會導緻程式的可讀性變差。

可變參數清單

func printStrings(strings: String...) {
    print(strings)
}

printStrings("1","2","3")  //輸出 ["1", "2", "3"]
           

通過輸出我們可以看到,可變參數在函數體内是以數組的類型存在的。這點在官方文檔上有說明。

In-Out 參數

在預設的情況下,參數傳遞給方法後都是常量,也就是說不能在函數體裡面對參數進行修改。這個常量是個形參,不是之前的實參。

func add(first: Int, _ second: Int) -> Int{
    first =   //這句報錯
    return first + second
}
           

有一種情況,我們希望在方法裡面改變實參的值,是以有了inout關鍵字,這個關鍵字不能對可變參數添加,同時加上了這個keyword之後,不能再添加 var let,也不能有預設值。

然後調用的時候,這個參數必須傳遞一個變量,而不能是常量,并且在變量前加&。

func add(first: inout Int, _ second: Int) -> Int{
    first = 
    return first + second
}
var a = 
print("result = \(add(first: &a, 3)), a = \(a) " )//輸出 result = 5, a = 2 
           

關于In-Out,這個實作原理是先将實參copy,然後在方法體内處理,方法結束的時候,再把copy覆寫回原來的實參。是以如果你在方法體裡面去改變實參(通過某些方法獲得),那麼在方法結束的時候,你對實參的改變會被形參覆寫。建議不要在方法體裡面操作InOut參數的實參。

關于InOut參數的捕獲問題,請參見官方文檔

In-Out Parameters

函數類型

函數也是一種類型。函數類型由函數定義決定。比如

func add(first: inout Int, _ second: Int) -> Int{
    first = ;
    return first + second
}
           

它的函數類型為 (inout Int, Int) -> Int

如果沒有參數也沒有傳回值的函數,函數類型為 () -> void,也可以寫為 () -> ()

函數類型可以和基本類型一樣,用來定義變量。繼續利用上面定義的add函數

var mathFunc : (inout Int, Int) -> Int = add

var f = ;
var s = ;
let result = mathFunc(&f,s)  //使用函數類型
           

函數類型可以用做參數或傳回值,利用上面定義的mathFunc變量,可以有

func add(first: inout Int, _ second: Int) -> Int{
    first = ;
    return first + second
}

var mathFunc : (inout Int, Int) -> Int = add

func doMath(mathFunc: (inout Int, Int) -> Int, first: inout Int, second: Int) {
    print("mathFunc = \(mathFunc(&first,second))")
    print("first = \(first)")
}

var f = 1;
var s = 2;
doMath(mathFunc: mathFunc, first: &f, second: )
print("f = \(f)")
//輸出
//mathFunc = 4
//first=2
//f=2
           

嵌套函數

故名思議,就是在函數裡面再定義函數。這個嵌套函數可以在函數内部調用,也可以作為傳回值傳回,使得它可以在其他範圍内進行使用。例子如下

//定義了add 和 sub 兩個嵌套函數,然後用于傳回。如果輸入的不是”+“或”-“,那麼傳回一個nil。注意giveMeFunc傳回的是一個函數類型的可選類型

func giveMeFunc(opt: Character) -> ((Int, Int) -> Int)? {
    var method : ((Int, Int) -> Int)?
    switch opt {
    case "+" :
        func add(one: Int, _ two: Int) -> Int { return one + two }
        method = add
    case "-" :
        func sub(one: Int, _ two: Int) -> Int { return one - two }
        method = sub
    default :
        method = nil
    }
    return method
}

if let m = giveMeFunc(opt:"-") {
    print(m(1, 2))  // 列印  -1, 留意一下,這裡沒有Argument Label。
}
           

操作符方法(Operator Methods)

swift和C++一樣,可以定義操作符函數。操作符指的是+,-,/,%,+=等等。一般我們這些操作符是給數字類型使用的。但是有了操作符函數之後,我們可以自定義這類符号的運算規則。下面是官方的示例:

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

上面的例子定義了一個操作符函數 + ,這個操作符函數的參數清單裡面有兩個參數left和right。分别代表着+号左右兩邊的兩個參數。通過這個函數,我們可以直接将兩個Vector示例進行相加。如下:

let vector = Vector2D(x: , y: )
let anotherVector = Vector2D(x: , y: )
let combinedVector = vector + anotherVector
// combinedVector is a Vector2D instance with values of (, )
           

除了這種要接受兩個參數的操作符之外,還要一些隻有一個參數的操作符,比如 -,++,–等等。但是這類操作符有兩類:字首(Prefix)和字尾(Postfix),比如–a,i++;

這類操作符的定義要加上prefix或postfix關鍵字。文法如下:

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

上面定義了一個字首的 - 操作符函數。用來将一個向量取反。後置操作符的關鍵字是postfix,中間操作符的關鍵字是infix。

另外還有一種計算并指派的操作符,比如++,+=等等。這類的操作符會對其中的一個操作對象進行操作後的指派。是以必須将參數設定為inout

extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}
           

除了Swift已經定義的操作符之外,還可以自己定義操作符。比如下面定義了一個+++操作符。

prefix operator +++ 
           

上面的隻是定義,我們還需要實作這個操作符所做的事情。

extension Vector2D {
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}
           

但是這個自定義的操作符有一些規定。

自定義的操作符可以由/, =, -, +, !, *, %, <, >, &, |, ^, ?,~,和某些Unicode 字元開始,至于是哪些字元可以參考官網。點選網頁,在網頁最下面

在這些字元之後,可以接上unicode字元。

另外有一些禁止的規定:

1.不能重寫一個單單的 ? 号。(可以在字元裡面加入?号)

2.不能重寫這些操作符 =, ->, //, /, /, ., 但是可以重寫兩個或更多個點的操作符。

3. 不能以這些字元開頭 ?, <, &

4.不能以這些字元結尾 ?, >, !

在定義的操作符的時候末尾的那對大括号是有用的。在數學上,加減乘除是有優先級和結合規則的。同樣的,這裡的操作符也是。我們可以在定義操作符的大括号裡面定義這個操作符的優先級和結合規律。

infix operator +-: AdditionPrecedence
extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}
           

上面例子定義的是一個中間的操作符,它的結合規則是向左結合。優先級是AdditionPrecedence組,這個參考下面的Precedence Group Declaration連結。

前置操作符和後置操作符不能指定優先級,它們作用在同一個操作數,那麼先執行後置操作符。

參考網頁

Swift函數文檔

Swift進階操作符

Swift詞彙結構

Precedence Group Declaration