第五章 函數式編程-基礎5.1 函數式編程內容說明5.1.1 函數式編程內容5.1.2 函數式編程授課順序5.2 函數式編程介紹5.2.1 幾個概念的說明5.2.2 方法、函數、函數式編程和面向對象編程關系分析圖5.2.3 函數式編程小結5.3 為什么需要函數5.4 函數的定義5.4.1 函數的定義5.4.2 快速入門案例5.5 函數的調用機制5.5.1 函數的調用過程5.5.2 函數的遞歸調用5.5.3 遞歸練習題5.6 函數注意事項和細節討論5.7 函數練習題5.8 過程5.8.1 基本概念5.8.2 注意事項和細節說明5.9 惰性函數5.9.1 看一個應用場景5.9.2 畫圖說明(大數據推薦系統)5.9.3 Java 實現懶加載的代碼5.9.4 惰性函數介紹5.9.5 案例演示5.9.6 注意事項和細節5.10 異常5.10.1 介紹5.10.2 Java 異常處理回顧5.10.3 Java 異常處理的注意點5.10.4 Scala 異常處理舉例5.10.5 Scala 異常處理小結5.11 函數的練習題第六章 面向對象編程-基礎6.1 類與對象6.1.1 Scala 語言是面向對象的6.1.2 快速入門-面向對象的方式解決養貓問題6.1.3 類和對象的區別和聯系6.1.4 如何定義類6.1.5 屬性6.1.6 屬性/成員變量6.1.7 屬性的高級部分6.1.8 如何創建對象6.1.9 類和對象的內存分配機制(重要)6.2 方法6.2.1 基本說明和基本語法6.2.2 方法的調用機制原理6.2.3 方法練習題6.3 類與對象應用實例6.4 構造器6.4.1 看一個需求6.4.2 回顧-Java 構造器的介紹+基本語法+特點+案例6.4.3 Scala 構造器的介紹+基本語法+快速入門6.4.4 Scala 構造器注意事項和細節6.5 屬性高級6.5.1 構造器參數6.5.2 Bean 屬性6.6 Scala 對象創建的流程分析6.7 作業03
第五章 函數式編程-基礎
5.1 函數式編程內容說明
5.1.1 函數式編程內容
函數式編程-基礎
1、函數定義/聲明
2、函數運行機制
3、遞歸【難點:最短路徑,郵差問題,背包問題,迷宮問題,回溯】
4、過程
5、惰性函數和異常
函數式編程-高級
6、值函數(函數字面量)
7、高階函數
8、閉包
9、應用函數
10、柯里化函數,抽象控制…
5.1.2 函數式編程授課順序
1、在 scala 中,函數式編程和面向對象編程融合在一起,學習函數式編程式需要 oop 的知識,同樣學習 oop 需要函數式編程的基礎。[矛盾]
2、二者關系如下圖:

3、授課順序:函數式編程基礎 -> 面向對象編程 -> 函數式編程高級
5.2 函數式編程介紹
5.2.1 幾個概念的說明
在學習 Scala 中將方法、函數、函數式編程和面向對象編程明確一下:
1、在 scala 中,方法
和函數
幾乎可以等同(比如他們的定義、使用、運行機制都一樣的),只是函數的使用方式更加的靈活多樣。
2、函數式編程
是從編程方式(范式)的角度來談的,可以這樣理解:函數式編程把函數當做一等公民,充分利用函數、支持的函數的多種使用方式
。
比如:在 Scala 當中,函數是一等公民,像變量一樣,既可以作為函數的參數使用,也可以將函數賦值給一個變量,函數的創建不用依賴於類或者對象,而在 Java 當中,函數的創建則要依賴於類、抽象類或者接口。
3、面向對象編程
是以對象為基礎的編程方式。
4、在 scala 中函數式編程和面向對象編程融合在一起了。
示例代碼如下:
package com.atguigu.chapter05
object Method2Function {
def main(args: Array[String]): Unit = {
// 傳統的方式使用方法
// 先創建一個對象
val dog = new Dog
println(dog.sum(10, 20))
// 方法轉成函數后使用函數
val f1 = dog.sum _
println("f1=" + f1) // f1=<function2>
println("f1=" + f1(50, 60))
// 直接寫一個函數並使用函數
// 格式:val f2 = (Int, Int) => {}
val f2 = (n1: Int, n2: Int) => {
n1 + n2 // 函數體
}
println("f2=" + f2) // f2=<function2>
println("f2=" + f2(80, 90))
}
}
class Dog {
// 方法
def sum(n1: Int, n2: Int): Int = {
n1 + n2 // 方法體
}
}
輸出結果如下:
30
f1=<function2>
f1=110
f2=<function2>
f2=170
5.2.2 方法、函數、函數式編程和面向對象編程關系分析圖
在學習 Scala 中將方法、函數、函數式編程和面向對象編程關系分析圖如下:

5.2.3 函數式編程小結
1、“函數式編程”是一種“編程范式”(programming paradigm)。
2、它屬於“結構化編程”的一種,主要思想是把運算過程盡量寫成一系列嵌套的函數調用。
3、函數式編程中,將函數也當做數據類型
,因此可以接受函數當作輸入(參數)和輸出(返回值)。
4、函數式編程中,最重要的就是函數。
5.3 為什么需要函數

學習一個技術或者知識點的流程:

5.4 函數的定義
5.4.1 函數的定義

5.4.2 快速入門案例
使用函數完全前面的案例。
示例代碼如下:
package com.atguigu.chapter05
object FunDemo01 {
def main(args: Array[String]): Unit = {
println("" + getRes(10, 20, '+'))
}
// 定義一個函數/方法
def getRes(n1: Int, n2: Int, oper: Char) = { // 返回值形式2: = 表示返回值類型不確定,使用類型推導完成。
if (oper == '+') {
// return n1 + n2 // return 關鍵字可以寫可以不寫
n1 + n2
} else if (oper == '-') {
n1 - n2
} else {
// 返回 null
null
}
}
}
5.5 函數的調用機制

5.5.1 函數的調用過程
為了讓大家更好的理解函數調用機制,看1個案例,並畫出示意圖,這個很重要,比如 getSum 計算兩個數的和,並返回結果。

5.5.2 函數的遞歸調用

注意
:Struts2 中的攔截器的底層實現機制就是把一個對象放到堆中,然后不停的開棧去指向該對象的內存地址,每一個棧都有機會去修改該對象的值。
函數遞歸需要遵守的重要原則(總結)
1、程序執行一個函數時,就創建一個新的受保護的獨立空間(新函數棧)。
2、函數的局部變量是獨立的,不會相互影響。
3、遞歸必須向退出遞歸的條件逼近,否則就是無限遞歸,死龜了:)
4、當一個函數執行完畢,或者遇到 return,就會返回,遵守誰調用,就將結果返回給誰。
5.5.3 遞歸練習題
題1:斐波那契數,請使用遞歸的方式,求出斐波那契數1,1,2,3,5,8,13…給你一個整數n,求出它的斐波那契數是多少?
示例代碼如下:
package com.atguigu.chapter05.recursive
import scala.io.StdIn
/**
* 題1:斐波那契數,請使用遞歸的方式,求出斐波那契數1,1,2,3,5,8,13... 給你一個整數n,求出它的斐波那契數是多少?
* 思路:f(1)=1, f(2)=1, f(3)=f(2)+f(1)=1+1=2, f(4)=f(2)+f(3)=1+2=3, ..., f(n)=f(n-1)+f(n-2)
*/
object Exercise01 {
def main(args: Array[String]): Unit = {
println("請輸入一個正整數:")
val n = StdIn.readInt()
printf("%d的斐波那契數是:%d", n, fbn(n))
}
def fbn(n: Int): Int = {
if (n == 1 || n == 2) {
1
} else {
fbn(n - 1) + fbn(n - 2)
}
}
}
題2:求函數值,已知 f(1)=3; f(n) = 2*f(n-1)+1; 請使用遞歸的思想編程,求出 f(n) 的值?
示例代碼如下:
package com.atguigu.chapter05.recursive
import scala.io.StdIn
/**
* 題2:求函數值,已知 f(1)=3; f(n) = 2*f(n-1)+1; 請使用遞歸的思想編程,求出 f(n) 的值?
* n=1, f(1)=3
* n=2, f(2)=2*f(1)+1=7
* n=3, f(3)=2*f(2)+1=15
*
*/
object Exercise02 {
def main(args: Array[String]): Unit = {
println("請輸入一個正整數:")
val n = StdIn.readInt()
printf("f(%d) 的值是:%d", n, f(n))
}
def f(n: Int): Int = {
if (n == 1) {
3
} else {
2 * f(n - 1) + 1
}
}
}
題3:猴子吃桃子問題:有一堆桃子,猴子第一天吃了其中的一半,並再多吃了一個!以后每天猴子都吃其中的一半,然后再多吃一個。當到第十天時,想再吃時(還沒吃),發現只有1個桃子了。問題:最初共多少個桃子?
示例代碼如下:
package com.atguigu.chapter05.recursive
import com.atguigu.chapter05.recursive.Exercise02.f
import scala.io.StdIn
/**
* 題3:猴子吃桃子問題:有一堆桃子,猴子第一天吃了其中的一半,並再多吃了一個!
* 以后每天猴子都吃其中的一半,然后再多吃一個。當到第十天時,想再吃時(還沒吃),發現只有1個桃子了。問題:最初共多少個桃子?
*
* day = 10 桃子有 1
* day = 9 桃子有 (day10的桃子 + 1) *2
* day = 8 桃子有 (day9 的桃子 + 1) *2
*
*/
object Exercise03 {
def main(args: Array[String]): Unit = {
println("最初共有:" + f(1) + "個桃子")
}
def f(n: Int): Int = {
if (n == 10) {
1
} else {
(f(n + 1) + 1) * 2
}
}
}
5.6 函數注意事項和細節討論
1、函數的形參列表可以是多個,如果函數沒有形參,調用時可以不帶()。
2、函數的形參列表和返回值列表的數據類型可以是值類型和引用類型。【案例演示】
示例代碼如下:
package com.atguigu.chapter05.fundetails
object Details01 {
def main(args: Array[String]): Unit = {
val tiger = new Tiger
val tiger2 = test01(10, tiger)
println(tiger2.name) // tom
println(tiger.name) // tom
println(tiger.hashCode() + " " + tiger2.hashCode()) // 2101440631 2101440631
}
// 2、函數的形參列表和返回值列表的數據類型可以是值類型和引用類型。
def test01(n: Int, tiger: Tiger): Tiger = {
println("n=" + n)
tiger.name = "tom"
tiger
// return tiger // 3、Scala 中的函數可以根據函數體最后一行代碼自行推斷函數返回值類型。那么在這種情況下,return 關鍵字可以省略。
}
}
class Tiger {
var name = ""
}
3、Scala 中的函數可以根據函數體最后一行代碼自行推斷函數返回值類型。那么在這種情況下,return 關鍵字可以省略。【案例同上】
4、因為 Scala 可以自行推斷,所以在省略 return 關鍵字的場合,返回值類型也可以省略。

5、如果函數明確使用 return 關鍵字,那么函數返回就不能使用自行推斷了,這時要明確寫成
: 返回值類型 =
,當然如果你什么都不寫,即使有 return,那么返回值為(),即這時 return 無效。
6、如果函數明確聲明無返回值(聲明 Unit),那么函數體中即使使用 return 關鍵字也不會有返回值。
示例代碼如下:
package com.atguigu.chapter05.fundetails
object Details02 {
def main(args: Array[String]): Unit = {
println(getSum2(10, 30)) // ()
println(getSum3(9, 9)) // ()
}
// 如果寫了 return,那么返回值類型就不能省略。
def getSum(n1: Int, n2: Int): Int = {
return n1 + n2
}
// 如果返回值這里什么什么都沒有寫,即表示該函數沒有返回值。
// 這時 return 無效
def getSum2(n1: Int, n2: Int) {
return n1 + n2
}
// 如果函數明確聲明無返回值(聲明Unit),那么函數體中即使使用 return 關鍵字也不會有返回值。
def getSum3(n1: Int, n2: Int): Unit = {
return n1 + n2
}
}
7、如果明確函數無返回值或不確定返回值類型,那么返回值類型可以省略(或聲明為 Any)。
示例代碼如下:
package com.atguigu.chapter05.fundetails
object Details03 {
def main(args: Array[String]): Unit = {
}
// 7、如果明確函數無返回值或不確定返回值類型,那么返回值類型可以省略(或聲明為 Any)。
def f3(s: String) = {
if (s.length >= 3)
s + "123"
else
3
}
def f4(s: String): Any = {
if (s.length >= 3)
s + "123"
else
3
}
}
8、Scala 語法中任何的語法結構都可以嵌套其他語法結構(很靈活),即:函數中可以再聲明/定義函數
,類中可以再聲明類
,方法中可以再聲明/定義方法
。
示例代碼如下:
package com.atguigu.chapter05.fundetails
object Details04 {
def main(args: Array[String]): Unit = { // public void main(String[] args)
def f1():Unit = { // private final void f1$1
println("f1")
}
def sayok(): Unit = { // private final void sayok$1
println("sayok~")
def sayok(): Unit = { // private final void sayok$2
println("sayok~~")
}
}
println("ok")
}
def sayok(): Unit = { // public void sayok()
println("sayok")
}
}
9、Scala 函數的形參,在聲明參數時,直接賦初始值(默認值),這時調用函數時,如果沒有指定實參,則會使用默認值。如果指定了實參,則實參會覆蓋默認值
。
示例代碼如下:
package com.atguigu.chapter05.fundetails
object Details05 {
def main(args: Array[String]): Unit = {
println(sayOk()) // jack ok!
println(sayOk("tom")) // tom ok!
}
def sayOk(name: String = "jack"): String = {
return name + " ok! "
}
}
10、如果函數存在多個參數,每一個參數都可以設定默認值,那么這個時候,傳遞的參數到底是覆蓋默認值,還是賦值給沒有默認值的參數,就不確定了(默認按照聲明順序[從左到右])。在這種情況下,可以采用帶名參數。
示例代碼如下:
package com.atguigu.chapter05.fundetails
object Details06 {
def main(args: Array[String]): Unit = {
mysqlCon()
mysqlCon("127.0.0.1", 7777) // 從左到右覆蓋
// 如果我們希望指定覆蓋某一個默認值,則使用帶名參數即可,比如只想修改用戶名和密碼,其他的不改
mysqlCon(user = "tom", pwd = "1234")
// 練習
// f6("v2") // 報錯,p2的值沒有指定
f6(p2 = "v2") // v1v2
}
def mysqlCon(add: String = "localhost", port: Int = 3306,
user: String = "root", pwd: String = "root"): Unit = {
println("add=" + add)
println("port=" + port)
println("user=" + user)
println("pwd=" + pwd)
}
def f6(p1: String = "v1", p2: String) {
println(p1 + p2);
}
}
11、scala 函數的形參默認是 val 的
,因此不能在函數中進行修改。
12、遞歸函數未執行之前是無法推斷出來結果類型,在使用時必須有明確的返回值類型。
示例代碼如下:
def f(n: Int) = { // 錯誤,遞歸不能使用類型推斷,必須指定返回的數據類型。
if (n <= 0)
1
else
n * f(n - 1)
}
13、Scala 函數支持可變參數
。
示例代碼如下:
// 支持0到多個參數
def sum(args: Int*): Int = {
}
// 支持1到多個參數
def sum(n1: Int, args: Int*): Int = {
}
說明:
1、args 是集合, 通過 for 循環 可以訪問到各個值
。【args 是參數名,可以任意起】
2、案例演示: 編寫一個函數 sum,可以求出 1 到多個 int 的和。
3、可變參數需要寫在形參列表的最后。
示例代碼如下:
package com.atguigu.chapter05.fundetails
object VarParameters {
def main(args: Array[String]): Unit = {
println(sum(10, 20, 30)) // 這里可變參數為2個
}
// 支持1到多個參數
def sum(n1: Int, args: Int*): Int = {
println("args.length=" + args.length)
var sum = n1
for (item <- args) {
sum += item
}
sum
}
}
5.7 函數練習題
判斷下面的代碼是否正確:
示例代碼如下:
object Hello01 {
def main(args: Array[String]): Unit = {
def f1 = "venassa"
println(f1) // 輸出 venassa
}
}
// 上面代碼 def f1 = "venassa" 等價於
def f1() = {
"venassa"
}
// 說明:
// 1、函數的形參列表可以是多個,如果函數沒有形參,函數可以不帶()。
// 2、函數的函數體只有一行代碼時,可以省略{}。
5.8 過程
5.8.1 基本概念
基本介紹:
將函數的返回類型為 Unit 的函數稱之為過程(procedure),如果明確函數沒有返回值,那么等號可以省略。
案例說明:
// f10 沒有返回值,可以使用 Unit 來說明
// 這時,這個函數我們也叫過程(procedure)
def f10(name: String): Unit = { // 如果明確函數沒有返回值,那么等號可以省略。
println(name+ "hello")
}
5.8.2 注意事項和細節說明
1、注意區分: 如果函數聲明時沒有返回值類型,但是有 = 號,可以進行類型推斷最后一行代碼。這時這個函數實際是有返回值的,該函數並不是過程。(這點在講解函數細節的時候講過的)
2、開發工具的自動代碼補全功能,雖然會自動加上 Unit,但是考慮到 Scala 語言的簡單,靈活,最好不加。
5.9 惰性函數
5.9.1 看一個應用場景
惰性計算(盡可能延遲表達式求值
)是許多函數式編程語言的特性。惰性集合在需要時提供其元素,無需預先計算它們,這帶來了一些好處。首先,您可以將耗時的計算推遲到絕對需要的時候。其次,您可以創造無限個集合,只要它們繼續收到請求,就會繼續提供元素。函數的惰性使用讓您能夠得到更高效的代碼。Java 並沒有為惰性提供原生支持,Scala 提供了。
5.9.2 畫圖說明(大數據推薦系統)

5.9.3 Java 實現懶加載的代碼
示例代碼如下:
package com.atguigu.chapter05;
public class LazyDemo {
private String property; // 屬性也可能是一個數據庫連接,文件等資源
public String getProperty() {
if (property == null) { // 如果沒有初始化過,那么就進行初始化
property = initProperty();
}
return property;
}
private String initProperty() {
return "property";
}
}
// 比如常用的【單例模式懶漢式】實現時就使用了上面類似的思路實現
5.9.4 惰性函數介紹
當函數返回值被聲明為 lazy 時
,函數的執行將被推遲,直到我們首次對此取值,該函數才會執行,這種函數我們稱之為惰性函數
。在 Java 的某些框架代碼中稱之為懶加載(延遲加載)
。
5.9.5 案例演示
示例代碼如下:
package com.atguigu.chapter05.mylazy
object LazyDemo01 {
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 20)
println("----------")
println("res=" + res) // 在要使用 res 前,才執行
}
def sum(n1: Int, n2: Int): Int = {
println("sum() 執行了..")
return n1 + n2
}
}
輸出結果如下:
----------
sum() 執行了..
res=30
5.9.6 注意事項和細節
1、lazy 不能修飾 var 類型的變量。
2、不但是在調用函數時,加了 lazy,會導致函數的執行被推遲,我們在聲明一個變量時,如果聲明了 lazy,那么變量值的分配也會推遲
。 比如 lazy val i = 10。
5.10 異常
5.10.1 介紹
Scala 提供 try 塊和 catch 塊來處理異常
。try 塊用於包含可能出錯的代碼。catch 塊用於處理 try 塊中發生的異常。可以根據需要在程序中有任意數量的 try…catch 塊。
語法處理上和 Java 類似,但是又不盡相同。
5.10.2 Java 異常處理回顧
示例代碼如下:
package com.atguigu.chapter05.exception;
public class JavaExceptionDemo {
public static void main(String[] args) {
try {
// 可疑代碼
int i = 0;
int b = 10;
int c = b / i; // 執行代碼時,會拋出 ArithmeticException 異常
} catch (Exception e) {
e.printStackTrace();
} finally {
// 最終要執行的代碼
System.out.println("java finally");
}
System.out.println("繼續執行");
}
}
輸出結果如下:
java finally
繼續執行
java.lang.ArithmeticException: / by zero
at com.atguigu.chapter05.exception.JavaExceptionDemo.main(JavaExceptionDemo.java:9)
5.10.3 Java 異常處理的注意點
1、java 語言按照 try-catch-catch…-finally 的方式來處理異常。
2、不管有沒有異常捕獲,都會執行 finally,因此通常可以在 finally 代碼塊中釋放資源。
3、可以有多個 catch,分別捕獲對應的異常,這時需要把范圍小的異常類寫在前面,把范圍大的異常類寫在后面,否則編譯錯誤。會提示 "Exception 'java.lang.xxxxxx' has already been caught"。
5.10.4 Scala 異常處理舉例
示例代碼如下:
package com.atguigu.chapter05.exception
object ScalaExceptionDemo {
def main(args: Array[String]): Unit = {
try {
val r = 10 / 0
} catch {
// 說明
// 1. 在 scala 中只有一個 catch
// 2. 在 catch 中有多個 case, 每個 case 可以匹配一種異常
// 3. => 關鍵符號,表示后面是對該異常的處理代碼塊
// 4. finally 最終要執行的代碼
case ex: ArithmeticException => { println("捕獲了除數為零的算數異常") } // 當對該異常的處理代碼塊為一行時,{}可以省略
case ex: Exception => println("捕獲了異常")
} finally {
// 最終要執行的代碼
println("scala finally")
}
System.out.println("繼續執行")
}
}
輸出結果如下:
捕獲了除數為零的算數異常
scala finally
繼續執行
5.10.5 Scala 異常處理小結
1、我們將可疑代碼封裝在 try 塊中
。在 try 塊之后使用了一個 catch 處理程序來捕獲異常。如果發生任何異常,catch 處理程序將處理它,異常處理了程序將不會異常終止
。
2、Scala 的異常的工作機制和 Java 一樣,但是 Scala 沒有“checked(編譯期)” 異常
,即 Scala 沒有編譯異常這個概念,異常都是在運行的時候捕獲處理。
3、Scala 用 throw 關鍵字,拋出一個異常對象
。所有異常都是 Throwable 的子類型。throw 表達式是有類型的,就是 Nothing,因為 Nothing 是所有類型的子類型,所以 throw 表達式可以用在需要類型的地方。
示例代碼如下:
package com.atguigu.chapter05.exception
object ThrowDemo {
def main(args: Array[String]): Unit = {
// val res = test()
// println(res.toString)
// println("繼續執行002") // 異常拋出了,但是沒有被處理,后續程序不能執行
// 如果我們希望在 test() 拋出異常后,后續代碼可以繼續執行,則我們需要如下處理
try {
test()
} catch {
case ex: Exception => {
println("捕獲到異常是:" + ex.getMessage)
println("繼續執行001")
}
case ex: ArithmeticException => println("得到一個算術異常(小范圍異常)")
} finally {
// 寫上對 try{} 中的資源的分配
}
println("繼續執行002")
}
def test(): Nothing = {
// Exception("異常出現")
throw new ArithmeticException("算術異常")
}
}
輸出結果如下:
捕獲到異常是:算術異常
繼續執行001
繼續執行002
4、在 Scala 里,借用了模式匹配的思想來做異常的匹配
,因此,在 catch 的代碼里,是一系列 case 子句來匹配異常。【前面案例可以看出這個特點,模式匹配我們后面詳解】,當匹配上后 => 有多條語句可以換行寫,類似 java 的 switch case x: 代碼塊…
5、異常捕捉的機制與其他語言中一樣,如果有異常發生,catch 子句是按次序捕捉的。因此,在 catch 子句中,越具體的異常越要靠前,越普遍的異常越靠后
,如果把越普遍的異常寫在前,把具體的異常寫在后,在 scala 中也不會報錯,但這樣是非常不好的編程風格。
6、finally 子句用於執行不管是正常處理還是有異常發生時都需要執行的步驟,一般用於對象的清理工作,這點和 Java 一樣。
7、Scala 提供了 throws 關鍵字來聲明異常。可以使用方法定義聲明異常。它向調用者函數提供了此方法可能引發此異常的信息。它有助於調用函數處理並將該代碼包含在 try-catch 塊中,以避免程序異常終止。在 scala 中,可以使用 throws 注釋來聲明異常。
示例代碼如下:
package com.atguigu.chapter05.exception
object ThrowsComment {
def main(args: Array[String]): Unit = {
f()
}
@throws(classOf[NumberFormatException]) // 等同於 Java 中 NumberFormatException.class
def f() = {
"abc".toInt
}
}
輸出結果如下:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:272)
at scala.collection.immutable.StringOps.toInt(StringOps.scala:29)
at com.atguigu.chapter05.exception.ThrowsComment$.f(ThrowsComment.scala:10)
at com.atguigu.chapter05.exception.ThrowsComment$.main(ThrowsComment.scala:5)
at com.atguigu.chapter05.exception.ThrowsComment.main(ThrowsComment.scala)
5.11 函數的練習題
1、函數可以沒有返回值案例,編寫一個函數,從終端輸入一個整數打印出對應的金子塔。
示例代碼如下:
package com.atguigu.chapter05.exercises
import scala.io.StdIn
/**
* 1、函數可以沒有返回值案例,編寫一個函數,從終端輸入一個整數打印出對應的金子塔。
* 思路:本質是打印出所有的 n行m列 數據。 分別循環即可!
*/
object Exercise01 {
def main(args: Array[String]): Unit = {
println("請輸入一個整數n(n>=1):")
val n = StdIn.readInt()
printJin(n)
}
def printJin(n: Int): Unit = {
for (i <- 1 to n) { // 行數
for (j <- 1 to (n - i)) {
printf("-")
}
for (j <- 1 to (2 * i - 1)) { // 列數
printf("*")
}
for (j <- 1 to (n - i)) {
printf("-")
}
println()
}
}
}
輸出結果如下:
請輸入一個整數n(n>=1):
5
----*----
---***---
--*****--
-*******-
*********
2、編寫一個函數,從終端輸入一個整數(1—9),打印出對應的乘法表。
示例代碼如下:
package com.atguigu.chapter05.exercises
import scala.io.StdIn
/**
* 2、編寫一個函數,從終端輸入一個整數(1—9),打印出對應的乘法表。
*/
object Exercise01 {
def main(args: Array[String]): Unit = {
println("請輸入數字(1-9)之間:")
val n = StdIn.readInt()
print99(n)
}
def print99(n: Int): Unit = {
for (i <- 1 to n) {
for (j <- 1 to i) {
printf("%d * %d = %d\t", j, i, j * i)
}
println()
}
}
}
3、編寫函數,對給定的一個二維數組 (3×3) 轉置,這個題講數組的時候再完成。
1 2 3 1 4 7
4 5 6 2 5 8
7 8 9 3 6 9
第六章 面向對象編程-基礎
6.1 類與對象
看一個養貓貓問題:
張老太養了只貓貓:一只名字叫小白,今年3歲,白色。還有一只叫小花,今年10歲,花色。請編寫一個程序,當用戶輸入小貓的名字時,就顯示該貓的名字,年齡,顏色。如果用戶輸入的小貓名錯誤,則顯示張老太沒有這只貓貓。
問題:
1、貓有三個屬性,類型不一樣。
2、如果使用普通的變量就不好管理。
3、使用一種新的數據類型:
(1) 可以管理多個不同類型的數據 [屬性]。
(2) 可以對屬性進行操作 => 方法。
類與對象的關系示意圖

6.1.1 Scala 語言是面向對象的
1、Java 是面向對象的編程語言,由於歷史原因,Java 中還存在着非面向對象的內容:基本類型,null,靜態方法等。
2、Scala 語言來自於 Java,所以天生就是面向對象的語言,而且 Scala 是純粹的面向對象的語言,即在 Scala 中,一切皆為對象。
3、在面向對象的學習過程中可以對比着 Java 語言學習。
6.1.2 快速入門-面向對象的方式解決養貓問題
示例代碼如下:
package com.atguigu.chapter06.oop
object CatDemo {
def main(args: Array[String]): Unit = {
// 創建一只貓
val cat = new Cat
// 給貓的屬性賦值
// 說明
// 1. cat.name = "小白" 其實不是直接訪問屬性,而是等價於 cat.name_$eq("小白")
// 2. cat.name 等價於 cat.name()
cat.name = "小白"
cat.age = 3
cat.color = "白色"
printf("\n小貓的信息如下:%s %d %s", cat.name, cat.age, cat.color)
}
}
/* 反編譯查看源碼
public void main (String[] args) {
Cat cat = new Cat ();
cat.name_$eq ("小白");
cat.age_$eq (3);
cat.color_$eq ("白色");
}
*/
// 定義一個 Cat 類
// 一個 class Cat 對應的字節碼文件只有一個 Cat.class ,默認是public
class Cat {
// 定義/聲明三個屬性
// 說明
// 1. 當我們聲明了 var name: String 時,同時在底層對應生成 private name
// 2. 同時在底層會生成 兩個 public 方法 public String name() 類似 => getter 和 public void name_$eq(String x$1) => setter
var name: String = "" // Scala 中定義變量必須給初始值
var age: Int = _ // 下划線表示給 age 一個默認值,如果是 Int 類型,默認就是 0
var color: String = _ // 如果是 String 類型,默認值就是 null
}
/* 反編譯查看源碼
public class Cat {
private String name = "";
private int age;
private String color;
public String name() {
return this.name;
}
public void name_$eq(String x$1) {
this.name = x$1;
}
public int age() {
return this.age;
}
public void age_$eq(int x$1) {
this.age = x$1;
}
public String color() {
return this.color;
}
public void color_$eq(String x$1) {
this.color = x$1;
}
}
*/
輸出結果如下:
小貓的信息如下:小白 3 白色
6.1.3 類和對象的區別和聯系
通過上面的案例和講解我們可以看出:
1、類是抽象的,概念的,代表一類事物,比如人類,貓類…
2、對象是具體的,實際的,代表一個具體事物。
3、類是對象的模板,對象是類的一個個體,對應一個實例。
4、Scala 中類和對象的區別和聯系 和 Java 是一樣的。
6.1.4 如何定義類

我們可以通過 反編譯來看 scala 的類默認為 public 的特性。
6.1.5 屬性

示例代碼如下:
class Dog {
var name = "jack"
var lover = new Fish
}
class Fish {
}
6.1.6 屬性/成員變量

示例代碼如下:
package com.atguigu.chapter06.oop
object PropertyDemo {
def main(args: Array[String]): Unit = {
// val p1 = new Person
// println(p1.Name) // Null
// println(p1.address) // String 類型
val a = new A
println(a.var1) // null
println(a.var2) // 0
println(a.var3) // 0.0
println(a.var4) // false
// 不同對象的屬性是獨立,互不影響,一個對象對屬性的更改,不影響另外一個
// 創建兩個對象
var worker1 = new Worker
worker1.name = "jack"
var worker2 = new Worker
worker2.name = "tom"
}
}
class Person3 {
var age: Int = 10 // 給屬性賦初值,省略類型,會自動推導
var sal = 8090.9
var Name = null // Name 是什么類型
var address: String = null // ok
}
class A {
var var1: String = _ // null String 和 引用類型默認值是 null
var var2: Byte = _ // 0
var var3: Double = _ // 0.0
var var4: Boolean = _ // false
}
class Worker {
var name = ""
}
輸出結果如下:
null
0
0.0
false
6.1.7 屬性的高級部分
說明:屬性的高級部分和構造器(構造方法/函數) 相關,我們把屬性高級部分放到構造器那里講解。
6.1.8 如何創建對象

示例代碼如下:
package com.atguigu.chapter06.oop
object CreateObjDemo {
def main(args: Array[String]): Unit = {
val emp = new Emp // 此時的 emp 類型就是 Emp
// 如果我們希望將子類對象,交給父類引用,這時就需要寫上類型,不能省略!
val emp1: Person = new Emp
}
}
class Person {
}
class Emp extends Person {
}
6.1.9 類和對象的內存分配機制(重要)
內存布局圖:

示例代碼如下:
package com.atguigu.chapter06.oop
object MemState {
def main(args: Array[String]): Unit = {
val p2 = new Person2
p2.name = "jack"
p2.age= 10
val p1 = p2
println(p1 == p2) // true
println("p2.age=" + p2.age) // 10
println("p1.age=" + p1.age) // 10
}
}
class Person2 {
var name = ""
var age: Int = _ // 如果是用下划線的方式給默認值,則屬性必須指定類型,因為這有這樣,scala 底層才能夠進行類型推斷
}
6.2 方法
6.2.1 基本說明和基本語法

示例代碼如下:
package com.atguigu.chapter06.method
object MethodDemo01 {
def main(args: Array[String]): Unit = {
// 使用一把
val dog = new Dog
println(dog.cal(10, 20))
}
}
class Dog {
private var sal: Double = _
var food: String = _
def cal(n1: Int, n2: Int): Int = {
return n1 + n2
}
}
6.2.2 方法的調用機制原理

6.2.3 方法練習題

1~3題的示例代碼如下:
package com.atguigu.chapter06.method
/**
* 1、編寫類(MethodExec),編寫一個方法,方法不需要參數,在方法中打印一個10*8的矩形,在main方法中調用該方法。
*
* 2、修改上一個程序,編寫一個方法中,方法不需要參數,計算該矩形的面積,並將其作為方法返回值。在main方法中調用該方法,接收返回的面積值並打印(結果保留小數點2位)。
*
* 3、修改上一個程序,編寫一個方法,提供m和n兩個參數,方法中打印一個m*n的矩形,再編寫一個方法計算該矩形的面積(可以接收長len和寬width), 將其作為方法返回值。在main方法中調用該方法,接收返回的面積值並打印。
*/
object MethodDemo02 {
def main(args: Array[String]): Unit = {
val m = new MethodExec
m.printRect1()
m.len = 1.2
m.width = 3.4
println("面積=" + m.area1())
m.printRect2(5, 4)
println("面積=" + m.area2(1.2, 3.4))
}
}
class MethodExec {
var len = 0.0
var width = 0.0
def printRect1(): Unit = {
for (i <- 0 until 10) {
for (j <- 0 until 8) {
print("*")
}
println()
}
}
def area1(): Double = {
this.len * this.width.formatted("%.2f").toDouble
}
def printRect2(m: Int, n: Int): Unit = {
for (i <- 0 until m) {
for (j <- 0 until n) {
printf("*")
}
println()
}
}
def area2(len: Double, width: Double): Double = {
len * width.formatted("%.2f").toDouble
}
}
輸出結果如下:
********
********
********
********
********
********
********
********
********
********
面積=4.08
****
****
****
****
****
面積=4.08
4~6題的示例代碼原理同上1~3題,不在贅述!
6.3 類與對象應用實例

景區門票案例

小狗案列的示例代碼如下:
package com.atguigu.chapter06.dogcase
/**
* 小狗案例
*
* 編寫一個Dog類,包含name(String)、age(Int)、weight(Double)屬性。
* 類中聲明一個say方法,返回String類型,方法返回信息中包含所有屬性值。
* 在另一個DogCaseTest類中的main方法中,創建Dog對象,並訪問say方法和所有屬性,將調用結果打印輸出。
*/
object DogCaseTest {
def main(args: Array[String]): Unit = {
val dog = new Dog
dog.name = "泰斯特"
dog.age = 2
dog.weight = 50
println(dog.say())
}
}
class Dog {
var name = ""
var age = 0
var weight = 0.0
def say(): String = {
"小狗的信息是:name=" + this.name + "\tage=" + this.age + "\tweight=" + this.weight
}
}
輸出結果如下:
小狗的信息是:name=泰斯特 age=2 weight=50.0
盒子案列、景區門票案例的示例代碼原理同上小狗案例,不在贅述!
6.4 構造器
6.4.1 看一個需求
前面我們在創建 Person 的對象時,是先把一個對象創建好后,再給他的年齡和姓名屬性賦值,如果現在我要求,在創建人類的對象時,就直接指定這個對象的年齡和姓名,該怎么做? 這時就可以使用構造方法/構造器。
6.4.2 回顧-Java 構造器的介紹+基本語法+特點+案例
Java 構造器的介紹
構造器(constructor)又叫構造方法,是類的一種特殊的方法,它的主要作用是完成對新對象的初始化
。
Java 構造器的基本語法

Java 構造器的特點

Java 構造器的案例

6.4.3 Scala 構造器的介紹+基本語法+快速入門
Scala 構造器的介紹
和 Java 一樣,Scala 構造對象也需要調用構造方法,並且可以有任意多個構造方法(即 scala 中構造器也支持重載)。
Scala 類的構造器包括: 主構造器
和 輔助構造器
。
Scala 構造器的基本語法

Scala 構造器的快速入門
示例代碼如下:
package com.atguigu.chapter06.constructor
/**
* Scala構造器的快速入門:創建Person對象的同時初始化對象的age屬性值和name屬性值
*/
object ConstructorDemo01 {
def main(args: Array[String]): Unit = {
val p1 = new Person("bruce", 20)
println(p1)
}
}
class Person(inName: String, inAge: Int) {
var name: String = inName
var age: Int = inAge
// 重寫toString方法
override def toString: String = {
"name=" + this.name + "\tage=" + this.age
}
}
輸出結果如下:
name=bruce age=20
6.4.4 Scala 構造器注意事項和細節
1、Scala 構造器作用是完成對新對象的初始化,構造器沒有返回值。
2、主構造器的聲明直接放置於類名之后。【可以反編譯查看】
3、主構造器會執行類定義中的所有語句(除掉函數部分),這里可以體會到 Scala 的函數式編程和面向對象編程融合在一起,即:構造器也是方法(函數)
,傳遞參數和使用方法和前面的函數部分內容沒有區別。【案例演示+反編譯查看-語法糖】
4、如果主構造器無參數,小括號可省略,構建對象時調用的構造方法的小括號也可以省略。
5、輔助構造器名稱為 this(這個和 Java 是不一樣的),多個輔助構造器通過不同參數列表進行區分, 在底層就是f構造器重載。【案例演示+反編譯查看】
示例代碼如下:
package com.atguigu.chapter06.constructor
object ConstructorDemo02 {
def main(args: Array[String]): Unit = {
// val a = new A
val aa = new A("jack")
// 執行順序:
// 1、bbb 父類構造器
// 2、A 子類主構造器
// 3、aaa 子類輔助構造器
}
}
class B {
println("bbb")
}
class A extends B {
println("A")
def this(name: String) {
this // 調用A的主構造器,其根本原因就是實現子類與父類之間的繼承關系,不然繼承關系就斷了!!!
println("aaa")
}
}
輸出結果如下:
bbb
A
aaa
示例代碼如下:
package com.atguigu.chapter06.constructor
object ConstructorDemo03 {
def main(args: Array[String]): Unit = {
val p1 = new Person("scott")
p1.showInfo()
}
}
class Person() {
var name: String = _
var age: Int = _
def this(name: String) {
// 輔助構造器無論是直接或間接,最終都一定要調用主構造器,執行主構造器的邏輯
// 而且需要放在輔助構造器的第一行[這點和 java 一樣,java 中一個構造器要調用同類的其它構造器,也需要放在第一行]
this() // 直接調用主構造器
this.name = name
}
def this(name: String, age: Int) {
this() // 直接調用主構造器
this.name = name
this.age = age
}
def this(age: Int) {
this("匿名") // 間接調用主構造器,因為 def this(name: String) 中直接調用了主構造器
this.age = age
}
def showInfo(): Unit = {
println("person信息如下:")
println("name=" + this.name)
println("age=" + this.age)
}
}
輸出結果如下:
person信息如下:
name=scott
age=0
6、如果想讓主構造器變成私有的,可以在()之前加上 private,這樣用戶只能通過輔助構造器來構造對象了。【反編譯查看】
7、輔助構造器的聲明不能和主構造器的聲明(即形參列表)一致,會發生錯誤(即構造器名重復)。
6.5 屬性高級
前面我們講過屬性了,這里我們再對屬性的內容做一個加強。
6.5.1 構造器參數

示例代碼如下:
package com.atguigu.chapter06.constructor
object ConstructorDemo04 {
def main(args: Array[String]): Unit = {
val worker1 = new Worker1("smith1")
worker1.name // 不能訪問 inName
val worker2 = new Worker2("smith2")
worker2.inName // 可以訪問 inName
println("hello!")
val worker3 = new Worker3("jack")
worker3.inName = "mary"
println(worker3.inName)
}
}
// 1. 如果 主構造器是 Worker1(inName: String),那么 inName 就是一個局部變量。
class Worker1(inName: String) {
var name = inName
}
// 2. 如果 主構造器是 Worker2(val inName: String),那么 inName 就是 Worker2 的一個 private 的只讀屬性。
class Worker2(val inName: String) {
var name = inName
}
// 3. 如果 主構造器是 Worker3(var inName: String),那么 inName 就是 Worker3 的一個 private 的可以讀寫屬性。
class Worker3(var inName: String) {
var name = inName
}
6.5.2 Bean 屬性

示例代碼如下:
package com.atguigu.chapter06.constructor
import scala.beans.BeanProperty
object BeanPropertyDemo {
def main(args: Array[String]): Unit = {
val car = new Car
car.name = "寶馬"
println(car.name)
// 使用 @BeanProperty 自動生成 getXxx() 和 setXxx()
car.setName("奔馳")
println(car.getName())
}
}
class Car {
@BeanProperty var name: String = null
}
6.6 Scala 對象創建的流程分析

6.7 作業03
1、一個數字如果為正數,則它的 signum 為1.0,如果是負數,則 signum 為-1.0,如果為0,則 signum 為0.0。編寫一個函數來計算這個值。
示例代碼如下:
package com.atguigu.chapter06.exercises
import scala.io.StdIn
/**
* 1、一個數字如果為正數,則它的 signum 為1,如果是負數,則 signum 為-1,如果為0,則 signum 為0。編寫一個函數來計算這個值。
*/
object Exercise01 {
def main(args: Array[String]): Unit = {
println("請輸入一個數字:")
val n = StdIn.readDouble()
println("該數的 signum 為:" + signum(n))
}
def signum(n: Double): Double = {
if (n > 0) {
1
} else if (n < 0) {
-1
} else {
0
}
}
}
輸出結果如下:
請輸入一個數字:
0
該數的 signum 為:0.0
請輸入一個數字:
5
該數的 signum 為:1.0
請輸入一個數字:
-3
該數的 signum 為:-1.0
2、一個空的塊表達式{}
的值是什么?類型是什么?
示例代碼如下:
package com.atguigu.chapter06.exercises
/**
* 2、一個空的塊表達式 {} 的值是什么?類型是什么?
*/
object Exercise02 {
def main(args: Array[String]): Unit = {
val t = {}
println("t=" + t) // t=()
println(t.isInstanceOf[Unit]) // true
}
}
3、針對下列 Java 循環編寫一個 Scala 版本:
for (int i=10; i>=0; i–-) {
System.out.println(i);
}
示例代碼如下:
package com.atguigu.chapter06.exercises
/**
* 3、針對下列 Java 循環編寫一個 Scala 版本:
* for (int i=10; i>=0; i–-) {
* System.out.println(i);
* }
*/
object Exercise03 {
def main(args: Array[String]): Unit = {
// 方式一:
for (i <- 0 to 10) {
println("i=" + (10 - i))
}
println("----------")
// 方式二:
for (i <- 0 to 10 reverse) { // 逆序
println("i=" + i)
}
// 定義一個 List 集合
val list = List(1, 2, 3)
println(list) // List(1, 2, 3)
println(list.reverse) // List(3, 2, 1)
}
}
4、編寫一個過程 countdown(n: Int),打印從 n 到 0 的數字。
示例代碼如下:
package com.atguigu.chapter06.exercises
import scala.io.StdIn
/**
* 4、編寫一個過程 countdown(n:Int),打印從 n 到 0 的數字。
*/
object Exercise04 {
def main(args: Array[String]): Unit = {
val m=3
val res1 = (0 to m).reverse
println(res1) // Range(3, 2, 1, 0)
// foreach
// foreach 函數可以接收 (f: Int => U),即接收一個輸入參數為 Int,輸出參數為 Unit 的函數
// 下面這句代碼的含義是:
// 1、將 res1 的每個元素依次遍歷出來,傳遞給 println(x)
// 調用系統的 println 函數
res1.foreach(println)
// 調用自定義的 println 函數
res1.foreach(myPrintln)
println("----------")
println("請輸入一個數字:")
val n = StdIn.readInt()
println("----------")
countdown(n)
println("----------")
countdown2(n)
println("----------")
countdown3(n)
}
// 自定義一個 println 函數
def myPrintln(n: Int): Unit = {
println(n)
}
// 方式一:
def countdown(n: Int): Unit = {
for (i <- 0 to n) {
println(n-i)
}
}
// 方式二:
def countdown2(n: Int): Unit = {
for (i <- 0 to n reverse) {
println(i)
}
}
// 方式三:
def countdown3(n: Int): Unit = {
// 說明
// 這里使用到高階函數的特性
(0 to n).reverse.foreach(println)
}
}
5、編寫一個 for 循環,計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。舉例來說,"Hello" 中所有字符串的乘積為 9415087488L。
示例代碼如下:
package com.atguigu.chapter06.exercises
import scala.io.StdIn
/**
* 5、編寫一個 for 循環,計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。舉例來說,"Hello" 中所有字符串的乘積為 9415087488L。
*/
object Exercise05 {
def main(args: Array[String]): Unit = {
println("請輸入一行字符串:")
val str = StdIn.readLine()
println("該字符串中所有字母的 Unicode 代碼的乘積為:" + unicode(str))
unicode2()
}
// 方式一:
def unicode(str: String): Long = {
var res:Long = 1
for (i <- 0 to str.length - 1) { // 索引從0開始
var s = str.charAt(i).toLong
res *= s
}
res
}
// 方式二:
def unicode2() = {
var res:Long = 1
for (i <- "Hello") {
res *= i.toLong
}
println("res=" + res)
}
}
輸出結果如下:
請輸入一行字符串:
Hello
該字符串中所有字母的 Unicode 代碼的乘積為:9415087488
res=9415087488
6、同樣是解決前一個練習的問題,請用 StringOps 的 foreach 方式解決。
示例代碼如下:
package com.atguigu.chapter06.exercises
import scala.io.StdIn
/**
* 6、同樣是解決前一個練習的問題,請用 StringOps 的 foreach 方式解決。
*/
object Exercise06 {
def main(args: Array[String]): Unit = {
var res1: Long = 1
// 說明
// 方式一:
// "Hello".foreach((_) => {res *= _.toLong})
"Hello".foreach(res1 *= _.toLong)
println("res1=" + res1)
// 方式二:
var res2 = 1L
"Hello".foreach(myCount)
println("res1=" + res2)
def myCount(char: Char): Unit = {
res2 *= char.toLong
}
}
}
輸出結果如下:
res1=9415087488
res1=9415087488
7、編寫一個函數 product(str: String),計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。
示例代碼如下:
package com.atguigu.chapter06.exercises
import scala.io.StdIn
/**
* 7、編寫一個函數 product(str: String),計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。
*/
object Exercise07 {
def main(args: Array[String]): Unit = {
println("請輸入一行字符串:")
val str = StdIn.readLine()
println("該字符串中所有字母的 Unicode 代碼的乘積為:" + product1(str))
println("該字符串中所有字母的 Unicode 代碼的乘積為:" + product2(str))
}
// 方式一:
def product1(str: String): Long = {
var multi = 1L
for (i <- 0 to str.length - 1) { // 索引從0開始
var s = str.charAt(i).toLong
multi *= s
}
multi
}
// 方式二:
def product2(str: String): Long = {
var multi = 1L
for (i <- str) {
multi *= i.toLong
}
multi
}
}
輸出結果如下:
請輸入一行字符串:
Hello
該字符串中所有字母的 Unicode 代碼的乘積為:9415087488
該字符串中所有字母的 Unicode 代碼的乘積為:9415087488
8、把7練習中的函數改成遞歸函數。
示例代碼如下:
package com.atguigu.chapter06.exercises
/**
* 8、把7練習中的函數改成遞歸函數。
*/
object Exercise08 {
def main(args: Array[String]): Unit = {
println("res=" + product("Hello")) // res=9415087488
println("Hello".take(1)) // H 獲取的是該字符串的第一個字符串
println("Hello".drop(1)) // ello 獲取的是該字符串的除第一個字符串之外的剩余字符串
}
def product(str: String): Long = {
if (str.length == 1) return str.charAt(0).toLong
else str.take(1).charAt(0).toLong * product(str.drop(1))
}
}
輸出結果如下:
res=9415087488
H
ello
9、編寫函數計算,其中 n 是整數,使用如下的遞歸定義:

示例代碼如下:
package com.atguigu.chapter06.exercises
/**
* 9、編寫函數計算,其中 n 是整數,使用如下的遞歸定義:
*/
object Exercise09 {
def main(args: Array[String]): Unit = {
println(mi(2.5, 3))
}
// 遞歸的妙用:求 x 的 n 次方,厲害啊!!!
def mi(x: Double, n: Int): Double = {
if (n == 0) 1 // x 的 0 次方等於 1
else if (n > 0) x * mi(x, n - 1)
else 1 / mi(x, -n)
}
}
注意:本題可以用於好好理解“遞歸”的妙用!!!
Java 與 Scala 在函數層面上的不同體現:
// 在 Java 中
函數(接收參數)
// 在 Scala 中
集合.函數(函數)
如下圖所示:
