【Scala】03 函式
阿新 • • 發佈:2021-07-11
1、Scala的方法語法:
object Hello { def main(args : Array[String]) : Unit = { // scala 允許在方法的宣告中再宣告方法,並且呼叫 def someFunction(x : Int, y : String): Unit = { println("this function has been called! (In this main method)") } // 呼叫方法中宣告的 someFunction(100, "str") // 呼叫這個物件宣告的 val returnVal = Hello.someFunction(2, "ol") }// 在物件中也可以宣告方法,支援和方法中重名 def someFunction(x : Int, y : String): Int = { println("this function has been called! (In this single instance)") return 100 } }
2、引數和返回值
// 沒有引數 沒有返回值 def method01(): Unit = { print("method01") } // 有引數 沒有返回值 def method02(x : Int, y : String): Unit = { print("method02") } // 有引數 有返回值 def method03(x : Int, y : String): Int = { print("method01") return 100 } // 沒有引數 有返回值 def method04(): Int = { return 300 }
3、引數特性
// 在引數型別後面加上*號表示該引數是可變引數 def method(str : String*): Unit = { println(str) } method("hello", "scala") // 列印結果是一個數組序列 ArraySeq(hello, scala)// 多個引數必須將可變引數放在最後一個 def method2(x : Int, y : Double, string: String*): Unit = { } // 預設引數 def method3(name : String = "這是預設值"): Unit = { println(name) } method3() // 這樣無參也可以被呼叫,因為已經設定了一個預設值在這裡 C++是支援這種操作的 method3("jojo") // 帶名引數,這個引數的意思是,你可以在呼叫的時候強制指定注入的引數是給哪個引數的 def method4(name : String, age : Int, gender : Boolean): Unit = { } // 帶名引數的意義就在於支援打亂順序入參 method4(gender = true, name = "張三", age = 30)
4、函式特性:
// Lambda表示式特性 // 1、可以省略return,Scala支援 函式體的最後一段程式碼最為返回的結果 def f0(str : String) : String = { str // 這裡就省略的了 return } println(f0("param")) // 2、函式體只有一段程式碼可以省略闊話表示 def f1(str : String) : String = str // 3、如果型別可以推斷出來,則不需要強制宣告返回型別是什麼 def f2(str : String) = str // 4、寫了Return就必須宣告返回型別 def f3(str : String) : String = return str // 5、明確了返回型別為Unit,則 return沒有任何意義 def f4(str : String) : Unit = return str // 這個str無意義 // 6、Unit返回型別 可以直接向Java一樣宣告 def f5(str : String) { println(str) } // 7、沒有引數,但是宣告的時候寫了,呼叫可以沒有括號,加括號也行 def f6() { println("str") } f6() // 這樣呼叫正常 f6 // 這樣呼叫也沒問題 // 8、如果宣告的時候就沒有引數列表,呼叫絕不能寫括號 def f7 { println("str") } f7 // 呼叫正常 // f7() // 這樣呼叫編譯報錯 // 如果不關心名稱,只需要邏輯處理,這裡也可以匿名宣告 (name : String) => { println(name) }
5、函式可以作為引數注入
val funcRef = (string : String) => { println(string) } // 定義一個函式,引數可以是一個函式 def funcForFunc(function: String => Unit): Unit = { function("asdas"); } // 這裡很奇怪的一點是,引數為什麼可以在裡面注入,而外面只需要宣告入引數函式? funcForFunc(funcRef) // 我有點想明白了,這樣的話,引數是頂死的,但是允許套用不同的方法,只要方法符合這個引數的方法型別要求即可被套用 // 語法縮寫 // 1、引數型別可以省略不寫,只要能夠被推匯出來 funcForFunc((name) => {println(name)}) // 2、引數只有一個可以不寫引數括號 funcForFunc(name => {println(name)}) // 3、執行的語句只有一行可以不寫花括號表示 funcForFunc(name => println(name)) // 4、引數只使用過一次的情況,可以不宣告引數了,直接使用下劃線表示 funcForFunc(println(_)) // 5、最後是呼叫推斷 funcForFunc(println)
用法案例介紹:
// 定義一個二元運算的函式,只操作固定的1和2個值,具體的運算由注入的函式實現 def calcForThis(fun : (Int, Int) => Int) : Int = { fun(100, 200) } val sum = (a : Int, b : Int) => a + b val minus = (a : Int, b : Int) => a - b val max = (a : Int, b : Int) => if (a > b) a else b val min = (a : Int, b : Int) => if (a > b) b else a // 呼叫 val s = calcForThis(sum) // val s = calcForThis(minus) // val s = calcForThis(max) // val s = calcForThis(min) // 匿名函式簡化 // 原始完整呼叫 println(calcForThis((a : Int, b : Int) => a + b)) // 對匿名簡化 println(calcForThis((a, b) => a + b)) // 引數簡化 println(calcForThis( _ + _))
這和Java或者之前學習的程式設計不太一樣
我們建立各種方法,是為了對引數進行處理,方法是為了封裝邏輯
現在感覺是反過來,引數是既定的,封裝的成方法了,這個感覺寫起來像那個介面的感覺一樣
6、函式的進一步操作
// 1、在函式體中宣告函式,然後又呼叫函式 def foo() { println("foo ...") } foo() // 2、作為值進行傳遞 def foo2() : Int = { 100 } val res = foo2() // 將函式的返回值傳遞給 res val res2 = foo2() _ // 將函式自己作為值傳遞給 res2 val res3:() => Int = foo2 // 如果明確變數型別? 可以不使用下劃線將函式作為整體傳遞給引數 // 3、函式入參 作為函式引數 def subjectFunction(paramFunction : (Int, Int) => Int): Int = { paramFunction(100, 314) // subjectFunction 主體函式, paramFunction引數函式 } subjectFunction(_ + _) subjectFunction(_ * _) // 4、函式作為返回值返回 def f1() = { // 宣告外層一個f1函式 def f2() = { // 在內層中宣告一個f2函式 } f2 _ // 並且返回f2這個函式 } val s = f1() // 呼叫就是把f2函式傳遞給s
7、對集合的一些處理操作
// 對陣列進行操作,如何操作作為一個抽象來定義,處理完畢返回一個新的陣列 def arrOp(arr : Array[Int], op : Int => Int) : Array[Int] = { for (elem <- arr) yield op(elem) } // 宣告一個加一操作 def increaseOne(elem : Int) = { elem + 1 } // 使用 val sourceArr = Array(1, 10, 30, 100) // 宣告一個原始的陣列 val newArr = arrOp(sourceArr, increaseOne) // 注意這裡給方法的時候是把方法本身傳遞,而不是呼叫 println(newArr.mkString(",")) // 簡化呼叫 val newArr2 = arrOp(sourceArr, _ + 1)
8、案例練習:
// 宣告匿名函式給fun變數 val fun = (a : Int, a2 : String, a3 : Char) => { if(a == 0 && a2 == "" && a3 == '0') false else true } // 呼叫 println(fun(0, "", '0'))
9、初見柯里化
// 但是這段宣告的語法看不太懂 def func(i: Int): String => (Char => Boolean) = { def f1(s : String) : Char => Boolean = { def f2(c : Char) : Boolean = { if(i == 0 && s == "" && c == '0') false else true } f2 } f1 } // 簡寫 def func2(i: Int): String => (Char => Boolean) = { (s : String) => { (c : Char) => { if(i == 0 && s == "" && c == '0') false else true } } } // 最外層有宣告引數型別,也可以被省略 def func3(i: Int): String => (Char => Boolean) = { s => c => if(i == 0 && s == "" && c == '0') false else true } // 柯里化? def func4(i: Int)(s: String)(c : Char):Boolean = { if(i == 0 && s == "" && c == '0') false else true } // 這裡肯定看不懂 println(func(0)("")('0')) // 這樣來拆解出來 是這樣呼叫的,只是沒有變數接受,直接匿名進行呼叫了 val resultFunction1 = func(0) val resultFunction2 = resultFunction1("") val res = resultFunction2('0') println(res)
10、案例:
// 閉包 & 柯里化 // 閉包概念 : 一個函式,訪問了外部的區域性變數的值,這個函式和所在的範圍稱為閉包 // 意義在於外部函式知曉完畢出棧之後,需要保留部分的引數內容,給內部函式進行計算和呼叫 // 案例 將固定加數作為另一個引數傳入,但是是作為第一層引數傳入 def addByParam():Int => Int = { val a = 4 def addByPa2(b : Int): Int = { a + b } addByPa2 } def addByParam2(a:Int):Int => Int = { def addByPa2(b : Int): Int = { a + b } addByPa2 } def addByParam3(a:Int):Int => Int = a + _ // 柯里化 把一個引數列表的多個引數,變成多個引數列表 val res = addByParam2(39)(42) def addCurrying(a : Int)(b : Int) = { a + b } addCurrying(39)(42)
11、遞迴 Recursive
/** * Scala的遞迴 * 1、方法呼叫的是自身 * 2、必須存在可以結束的邏輯 * 3、引數應該有規律 * 4、遞迴必須宣告返回型別 */ // 階乘案例 def factorial(n : Int): Int = { if (n == 0) return 1 factorial(n - 1) * n } // 尾遞迴 def tailFact(n : Int)= { def loop(n : Int, currRes : Int) : Int = { if (n == 0) return currRes loop(n - 1, currRes * n) } loop(n, 1) }
12、抽象控制
// 控制抽象 /** * 這裡演示一段Java寫法 */ val a = 100 // 宣告一個常量 def someFunction(s : Int) = { // 宣告函式 println(s) } someFunction(100) // 一般來說就是直接注入實際可見的值 someFunction(a) // 或者是變數 /** * Scala 希望不出現這些變數和字面值 * 因為函式代表了一切內容 */ def returnTen() = 10 someFunction(returnTen()) // 要以這種方法來進行入參的表達方式,就是控制抽象
13、傳名引數
// 傳名引數,強調的是引數是一段程式碼塊,執行包括程式碼塊中的內容 def fun(a : => Int) = { println(s"a : ${a}") println(s"a : ${a}") } def f1() = { println("f1 called") 12 } // 使用 fun(f1()) // f1被呼叫了兩遍 a即表示函式,出現一次呼叫一次 fun(23) // 為什麼可以傳遞23? /** * 因為23就是一個函式的返回 */ () => 23
14、實現自定義迴圈的案例
// 實現自定義迴圈 def customWhile(condition : Boolean):(=> Unit)=> Unit= { def doCustomLoop (operate : => Unit): Unit = { if (condition) { operate customWhile(condition) (operate) } } doCustomLoop _ } // 柯里化表達 def customWhile2(condition: => Boolean)(operate: => Unit): Unit = { if (condition) { operate customWhile2(condition)(operate) } } var n = 10 customWhile2(n > 0) { println(n) n -= 1 }
15、懶載入處理
val sum = (a : Int,b : Int) => { println("sum has been called") a + b } val minus = (a : Int,b : Int) => { println("minus has been called") a - b } val result1 : Int = minus(11, 31) // 惰性載入 lazy val result2 : Int = sum(11, 31) println("- - - 函式應該被呼叫了 - - -") println(s"函式應該被呼叫了 ${result2} ${result1}")