Generics(泛型)

//泛型 Generics

// “泛型代碼讓你能夠根據(jù)自定義的需求耀怜,編寫出適用于任意類型从绘、靈活可重用的函數(shù)及類型。它能讓你避免代碼的重復(fù)涣觉,用一種清晰和抽象的方式來表達(dá)代碼的意圖贵涵×兄”
//“泛型是 Swift 最強大的特性之一恰画,許多 Swift 標(biāo)準(zhǔn)庫是通過泛型代碼構(gòu)建的”
//“泛型的使用貫穿了整本語言手冊.例如,Swift 的 Array 和 Dictionary 都是泛型集合瓷马。你可以創(chuàng)建一個 Int 數(shù)組拴还,也可創(chuàng)建一個 String 數(shù)組,甚至可以是任意其他 Swift 類型的數(shù)組欧聘。同樣的自沧,你也可以創(chuàng)建存儲任意指定類型的字典

//1.泛型解決的問題

func swapTwoInts(_ a:inout Int, _ b: inout Int){
    let temperatyA = a
    a = b
    b = temperatyA
}

//“這個函數(shù)使用輸入輸出參數(shù)(inout)來交換 a 和 b 的值

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt),anotherInt is now \(anotherInt)")

//“誠然,swapTwoInts(::) 函數(shù)挺有用树瞭,但是它只能交換 Int 值拇厢,如果你想要交換兩個 String 值或者 Double值,就不得不寫更多的函數(shù)”
//“在實際應(yīng)用中晒喷,通常需要一個更實用更靈活的函數(shù)來交換兩個任意類型的值孝偎,幸運的是,泛型代碼幫你解決了這種問題”

//2. 泛型函數(shù)
//“泛型函數(shù)可以適用于任何類型

func swapTwoValues<T>(_ a:inout T,_ b:inout T){
    let temporaryA = a
    a = b
    b = temporaryA
}

//“swapTwoValues(::) 的函數(shù)主體和 swapTwoInts(::) 函數(shù)是一樣的凉敲,它們只在第一行有點不同衣盾,如下所示:
//func swapTwoInts(_ a: inout Int, _ b: inout Int)
//func swapTwoValues<T>(_ a: inout T, _ b: inout T)
//“這個函數(shù)的泛型版本使用了占位類型名(在這里用字母 T 來表示)來代替實際類型名(例如 Int、String 或 Double)爷抓。占位類型名沒有指明 T 必須是什么類型势决,但是它指明了 a 和 b 必須是同一類型 T,無論 T 代表什么類型蓝撇。只有 swapTwoValues(::) 函數(shù)在調(diào)用時果复,才能根據(jù)所傳入的實際類型決定 T 所代表的類型〔巢”
//“另外一個不同之處在于這個泛型函數(shù)名(swapTwoValues(::))后面跟著占位類型名(T)虽抄,并用尖括號括起來(<T>)。這個尖括號告訴 Swift 那個 T 是 swapTwoValues(::) 函數(shù)定義內(nèi)的一個占位類型名独柑,因此 Swift 不會去查找名為 T 的實際類型迈窟。”

var someString = "jack"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)

//“注意 上面定義的 swapTwoValues(::) 函數(shù)是受 swap(::) 函數(shù)啟發(fā)而實現(xiàn)的忌栅。后者存在于 Swift 標(biāo)準(zhǔn)庫车酣,你可以在你的應(yīng)用程序中使用它∷餍鳎”

//3. 類型參數(shù)
//“在上面的 swapTwoValues(::) 例子中湖员,占位類型 T 是類型參數(shù)的一個例子。類型參數(shù)指定并命名一個占位類型者春,并且緊隨在函數(shù)名后面破衔,使用一對尖括號括起來(例如 <T>)∏蹋”
//“一旦一個類型參數(shù)被指定,你可以用它來定義一個函數(shù)的參數(shù)類型(例如 swapTwoValues(::) 函數(shù)中的參數(shù) a 和 b),或者作為函數(shù)的返回類型拴袭,還可以用作函數(shù)主體中的注釋類型读第。在這些情況下,類型參數(shù)會在函數(shù)調(diào)用時被實際類型所替換拥刻×鳎”
//“你可提供多個類型參數(shù),將它們都寫在尖括號中般哼,用逗號分開吴汪。”

//4.命名類型參數(shù)
//“在大多數(shù)情況下蒸眠,類型參數(shù)具有一個描述性名字漾橙,例如 Dictionary<Key, Value> 中的 Key 和 Value,以及 Array<Element> 中的 Element楞卡,這可以告訴閱讀代碼的人這些類型參數(shù)和泛型函數(shù)之間的關(guān)系霜运。然而,當(dāng)它們之間沒有有意義的關(guān)系時蒋腮,通常使用單個字母來命名淘捡,例如 T、U池摧、V焦除,正如上面演示的 swapTwoValues(::) 函數(shù)中的 T 一樣∽魍”

//5. 泛型類型
//“除了泛型函數(shù)踢京,Swift 還允許你定義泛型類型。這些自定義類宦棺、結(jié)構(gòu)體和枚舉可以適用于任何類型瓣距,類似于 Array 和 Dictionary”
//“這部分內(nèi)容將向你展示如何編寫一個名為 Stack (棧)的泛型集合類型”
//“棧是一系列值的有序集合,和 Array 類似代咸,但它相比 Swift 的 Array 類型有更多的操作限制蹈丸。數(shù)組允許在數(shù)組的任意位置插入新元素或是刪除其中任意位置的元素。而棧只允許在集合的末端添加新的元素(稱之為入棧)呐芥。類似的逻杖,棧也只能從末端移除元素(稱之為出棧)∷嘉粒”

//“注意 棧的概念已被 UINavigationController 類用來構(gòu)造視圖控制器的導(dǎo)航結(jié)構(gòu)荸百。你通過調(diào)用 UINavigationController 的 pushViewController(:animated:) 方法來添加新的視圖控制器到導(dǎo)航棧,通過 popViewControllerAnimated(:) 方法來從導(dǎo)航棧中移除視圖控制器滨攻。每當(dāng)你需要一個嚴(yán)格的“后進(jìn)先出”方式來管理集合够话,棧都是最實用的模型蓝翰。”

struct IntStack{
    var items = [Int]()
    mutating func push(_ item:Int){
        items.append(item)
    }
    mutating func pop()->Int{
        return items.removeLast()
    }
}

//6.泛型版本

struct Stack<Element>{
    var items = [Element]()
    mutating func push(item:Element){
        items.append(item)
    }
    mutating func pop()->Element{
        return items.removeLast()
    }
}

//“注意女嘲,Stack 基本上和 IntStack 相同畜份,只是用占位類型參數(shù) Element 代替了實際的 Int 類型。這個類型參數(shù)包裹在緊隨結(jié)構(gòu)體名的一對尖括號里(<Element>)欣尼”ⅲ”
//“由于 Stack 是泛型類型,因此可以用來創(chuàng)建 Swift 中任意有效類型的棧愕鼓,就像 Array 和 Dictionary 那樣钙态。”
//“你可以通過在尖括號中寫出棧中需要存儲的數(shù)據(jù)類型來創(chuàng)建并初始化一個 Stack 實例”

var StackOfStings = Stack<String>()
StackOfStings.push(item: "aa")
StackOfStings.push(item: "bb")
StackOfStings.push(item: "cc")
StackOfStings.pop()

//7. 擴(kuò)展一個泛型類型
//“當(dāng)你擴(kuò)展一個泛型類型的時候菇晃,你并不需要在擴(kuò)展的定義中提供類型參數(shù)列表册倒。原始類型定義中聲明的類型參數(shù)列表在擴(kuò)展中可以直接使用,并且這些來自原始類型中的參數(shù)名稱會被用作原始定義中類型參數(shù)的引用”

extension Stack {
    var topItem : Element?{
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

//“注意谋旦,這個擴(kuò)展并沒有定義一個類型參數(shù)列表剩失。相反的,Stack 類型已有的類型參數(shù)名稱 Element册着,被用在擴(kuò)展中來表示計算型屬性 topItem 的可選類型拴孤。”

if let topItem = StackOfStings.topItem {
    print("the top item on stack is \(topItem)")
}
//打印  the top item on stack is bb

//8. 類型約束
//“swapTwoValues(::) 函數(shù)和 Stack 類型可以作用于任何類型甲捏。不過演熟,有的時候如果能將使用在泛型函數(shù)和泛型類型中的類型添加一個特定的類型約束,將會是非常有用的司顿。類型約束可以指定一個類型參數(shù)必須繼承自指定類芒粹,或者符合一個特定的協(xié)議或協(xié)議組合〈罅铮”

//“例如化漆,Swift 的 Dictionary 類型對字典的鍵的類型做了些限制。在字典的描述中钦奋,字典的鍵的類型必須是可哈希(hashable)的座云。也就是說,必須有一種方法能夠唯一地表示它付材。Dictionary 的鍵之所以要是可哈希的朦拖,是為了便于檢查字典是否已經(jīng)包含某個特定鍵的值。若沒有這個要求厌衔,Dictionary 將無法判斷是否可以插入或者替換某個指定鍵的值璧帝,也不能查找到已經(jīng)存儲在字典中的指定鍵的值「皇伲”
//“為了實現(xiàn)這個要求睬隶,一個類型約束被強制加到 Dictionary 的鍵類型上锣夹,要求其鍵類型必須符合 Hashable 協(xié)議,這是 Swift 標(biāo)準(zhǔn)庫中定義的一個特定協(xié)議理疙。所有的 Swift 基本類型(例如 String晕城、Int泞坦、Double 和 Bool)默認(rèn)都是可哈希的窖贤。”

//8.1 類型約束語法
//“你可以在一個類型參數(shù)名后面放置一個類名或者協(xié)議名贰锁,并用冒號進(jìn)行分隔赃梧,來定義類型約束,它們將成為類型參數(shù)列表的一部分”

//func someFunction<T:SomeClass,U:SomeProtocol>(someT:T,someU:U){
     //這里是泛型的函數(shù)體
//}

//“第一個類型參數(shù) T豌熄,有一個要求 T 必須是 SomeClass 子類的類型約束授嘀;第二個類型參數(shù) U,有一個要求 U 必須符合 SomeProtocol 協(xié)議的類型約束锣险√阒澹”

//8.2 類型約束實踐

func findIndex(ofString valueToFind:String,in array:[String])->Int?{
    for (index,value) in array.enumerated() {
        if value == valueToFind{
            return index
        }
    }
    return nil
}
let strings = ["cat","dog","lima","jack"]
if let foundIndex = findIndex(ofString: "jack", in: strings) {
    print("the index of jack is \(foundIndex)")
}

//泛型版本

//func findIndexGenerics<T>(of valueToFind:T,in array:[T])->Int?{
//    for (index,value) in array.enumerated() {
//        if value == valueToFind{
//            return index
//        }
//    }
//    return nil
//}

//“上面所寫的函數(shù)無法通過編譯。問題出在相等性檢查上芯肤,即 "if value == valueToFind"巷折。不是所有的 Swift 類型都可以用等式符(==)進(jìn)行比較,“比如說,如果你創(chuàng)建一個自定義的類或結(jié)構(gòu)體來表示一個復(fù)雜的數(shù)據(jù)模型崖咨,那么 Swift 無法猜到對于這個類或結(jié)構(gòu)體而言“相等”意味著什么锻拘。正因如此,這部分代碼無法保證適用于每個可能的類型 T击蹲,當(dāng)你試圖編譯這部分代碼時會出現(xiàn)相應(yīng)的錯誤署拟。”
//“不過歌豺,所有的這些并不會讓我們無從下手推穷。Swift 標(biāo)準(zhǔn)庫中定義了一個 Equatable 協(xié)議,該協(xié)議要求任何遵循該協(xié)議的類型必須實現(xiàn)等式符(==)及不等符(!=)类咧,從而能對該類型的任意兩個值進(jìn)行比較馒铃。所有的 Swift 標(biāo)準(zhǔn)類型 自動支持 Equatable 協(xié)議÷痔”

func findIndexGenerices<T:Equatable>(array:[T],_ valuetoFind:T)->Int?{
    for (index,value) in array.enumerated() {
        if value == valuetoFind {
            return index
        }
    }
    return nil
}
let doubleIndex = findIndexGenerices(array: [3,14159,0.1,0.25], 9.3)
//doubleIndex的值為nil
let stingIndex = findIndexGenerices(array: ["mike","jordan","rose"], "rose")
//stingIndex類型為int骗露? 值為2

//9. 關(guān)聯(lián)類型
//“定義一個協(xié)議時,有的時候聲明一個或多個關(guān)聯(lián)類型作為協(xié)議定義的一部分將會非常有用”“關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位名(或者說別名)血巍,其代表的實際類型在協(xié)議被采納時才會被指定萧锉。你可以通過 associatedtype 關(guān)鍵字來指定關(guān)聯(lián)類型∈龉眩”

protocol Container{
    associatedtype itemType
    mutating func append(item:itemType)
    var count : Int{get}
    subscript(i:Int)->itemType{get}
}
struct IntStackGenerics:Container {
    var items = [Int]()
    mutating func push(item:Int){
        items.append(item)
    }
    mutating func pop()->Int{
        return items.removeLast()
    }
    
    //Container協(xié)議實現(xiàn)部分
    typealias itemType = Int
    mutating func append(item: Int) {
        self.push(item: item)
    }
    var count: Int {
        return items.count
    }
    subscript(i:Int)->Int{
        return items[i]
    }
}

//“IntStack 結(jié)構(gòu)體實現(xiàn)了 Container 協(xié)議的三個要求柿隙,其原有功能也不會和這些要求相沖突叶洞。
//此外,IntStack 在實現(xiàn) Container 的要求時禀崖,指定 ItemType 為 Int 類型衩辟,即 typealias ItemType = Int,從而將 Container 協(xié)議中抽象的 ItemType 類型轉(zhuǎn)換為具體的 Int 類型波附∫涨纾”
//“由于 Swift 的類型推斷,你實際上不用在 IntStack 的定義中聲明 ItemType 為 Int掸屡。因為 IntStack 符合 Container 協(xié)議的所有要求封寞,Swift 只需通過 append(_:) 方法的 item 參數(shù)類型和下標(biāo)返回值的類型,就可以推斷出 ItemType 的具體類型仅财。事實上狈究,如果你在上面的代碼中刪除了 typealias ItemType = Int 這一行,一切仍舊可以正常工作盏求,因為 Swift 清楚地知道 ItemType 應(yīng)該是哪種類型抖锥。”

//也可以讓泛型Stack結(jié)構(gòu)體遵從Container協(xié)議
struct StackGenerics<Element>:Container{
    //StackCenerics<Element>的原始部分
    var items = [Element]()
    mutating func push(item:Element){
        items.append(item)
    }
    mutating func pop()->Element{
        return items.removeLast()
    }
    //Container協(xié)議實現(xiàn)部分
   mutating func append(item: StackGenerics<Element>.itemType) {
        self.push(item: item)
    }
    var count: Int{
        return items.count
    }
    subscript(i:Int)->Element{
        return items[i]
    }
}

//“這一次碎罚,占位類型參數(shù) Element 被用作 append(_:) 方法的 item 參數(shù)和下標(biāo)的返回類型磅废。Swift 可以據(jù)此推斷出 Element 的類型即是 ItemType 的類型。

//9.2 “通過擴(kuò)展一個存在的類型來指定關(guān)聯(lián)類型”
//“通過擴(kuò)展添加協(xié)議一致性中描述了如何利用擴(kuò)展讓一個已存在的類型符合一個協(xié)議魂莫,這包括使用了關(guān)聯(lián)類型的協(xié)議还蹲。”
//“Swift 的 Array 類型已經(jīng)提供 append(:) 方法耙考,一個 count 屬性谜喊,以及一個接受 Int 類型索引值的下標(biāo)用以檢索其元素。這三個功能都符合 Container 協(xié)議的要求倦始,也就意味著你只需簡單地聲明 Array 采納該協(xié)議就可以擴(kuò)展 Array斗遏,使其遵從 Container 協(xié)議。你可以通過一個空擴(kuò)展來實現(xiàn)這點鞋邑,正如通過擴(kuò)展采納協(xié)議中的描述:”
extension Array:Container{}
//“如同上面的泛型 Stack 結(jié)構(gòu)體一樣诵次,Array 的 append(
:) 方法和下標(biāo)確保了 Swift 可以推斷出 ItemType 的類型。定義了這個擴(kuò)展后枚碗,你可以將任意 Array 當(dāng)作 Container 來使用逾一。”

//10.泛型Where語句
//“類型約束讓你能夠為泛型函數(shù)或泛型類型的類型參數(shù)定義一些強制要求肮雨∽穸拢”
//“為關(guān)聯(lián)類型定義約束也是非常有用的。你可以在參數(shù)列表中通過 where 子句為關(guān)聯(lián)類型定義約束”

//“你能通過 where 子句要求一個關(guān)聯(lián)類型遵從某個特定的協(xié)議,以及某個特定的類型參數(shù)和關(guān)聯(lián)類型必須類型相同”
//“你可以通過將 where 關(guān)鍵字緊跟在類型參數(shù)列表后面來定義 where 子句陌宿,where 子句后跟一個或者多個針對關(guān)聯(lián)類型的約束锡足,以及一個或多個類型參數(shù)和關(guān)聯(lián)類型間的相等關(guān)系】瞧海”
//“你可以在函數(shù)體或者類型的大括號之前添加 where 子句舶得。”

func allItemsMatch<C1:Container,C2:Container>(_ someContainer:C1,_ anotherContainer:C2)->Bool where C1.itemType == C2.itemType,C1.itemType:Equatable{
    //檢查兩個容器含有相同數(shù)量的元素
    if someContainer.count != anotherContainer.count {
        return false
    }
    //檢查每一對元素是否相等
    for i in 0..<someContainer.count{
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
/*
 “C1 必須符合 Container 協(xié)議(寫作 C1: Container)爽蝴。
  C2 必須符合 Container 協(xié)議(寫作 C2: Container)沐批。
  C1 的 ItemType 必須和 C2 的 ItemType類型相同(寫作 C1.ItemType == C2.ItemType)。
  C1 的 ItemType 必須符合 Equatable 協(xié)議(寫作 C1.ItemType: Equatable)霜瘪。
 */

//“第三個和第四個要求被定義為一個 where 子句珠插,寫在關(guān)鍵字 where 后面惧磺,它們也是泛型函數(shù)類型參數(shù)列表的一部分”

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颖对,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子磨隘,更是在濱河造成了極大的恐慌缤底,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件番捂,死亡現(xiàn)場離奇詭異个唧,居然都是意外死亡,警方通過查閱死者的電腦和手機设预,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門徙歼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鳖枕,你說我怎么就攤上這事魄梯。” “怎么了宾符?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵酿秸,是天一觀的道長。 經(jīng)常有香客問我魏烫,道長辣苏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任哄褒,我火速辦了婚禮稀蟋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呐赡。我一直安慰自己退客,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布罚舱。 她就那樣靜靜地躺著井辜,像睡著了一般绎谦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粥脚,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天窃肠,我揣著相機與錄音,去河邊找鬼刷允。 笑死冤留,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的树灶。 我是一名探鬼主播纤怒,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼天通!你這毒婦竟也來了泊窘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤像寒,失蹤者是張志新(化名)和其女友劉穎烘豹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诺祸,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡携悯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了筷笨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憔鬼。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胃夏,靈堂內(nèi)的尸體忽然破棺而出轴或,到底是詐尸還是另有隱情,我是刑警寧澤构订,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布侮叮,位于F島的核電站,受9級特大地震影響悼瘾,放射性物質(zhì)發(fā)生泄漏囊榜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一亥宿、第九天 我趴在偏房一處隱蔽的房頂上張望卸勺。 院中可真熱鬧,春花似錦烫扼、人聲如沸曙求。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悟狱。三九已至静浴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挤渐,已是汗流浹背苹享。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浴麻,地道東北人得问。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像软免,于是被迫代替她去往敵國和親宫纬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 泛型代碼可以確保你寫出靈活的膏萧,可重用的函數(shù)和定義出任何你所確定好的需求的類型漓骚。你的可以寫出避免重復(fù)的代碼,并且用一...
    iOS_Developer閱讀 800評論 0 0
  • 本章將會介紹 泛型所解決的問題泛型函數(shù)類型參數(shù)命名類型參數(shù)泛型類型擴(kuò)展一個泛型類型類型約束關(guān)聯(lián)類型泛型 Where...
    寒橋閱讀 639評論 0 2
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型向抢。泛型...
    果啤閱讀 678評論 0 0
  • 136.泛型 泛型代碼讓你可以寫出靈活,可重用的函數(shù)和類型,它們可以使用任何類型,受你定義的需求的約束认境。你可以寫出...
    無灃閱讀 1,472評論 0 4
  • 一般只問return true/false, 問number 都可以用動態(tài)規(guī)劃來做。 這題的誤區(qū)是容易以為是一個遞...
    98Future閱讀 197評論 0 0