Swift基礎知識相關(一) —— 泛型(一)

版本記錄

版本號 時間
V1.0 2019.07.12 星期五

前言

這個專題我們就一起看一下Swfit相關的基礎知識荧嵌。

開始

首先看下主要內容

主要內容:學習編寫函數和數據類型挣跋,同時做出最小的假設衷咽。 Swift泛型允許更少的bug和更加干凈清晰的代碼。

接著看下寫作環(huán)境

Swift 5, iOS 13, Xcode 11

Generic programming是一種編寫函數和數據類型的方法谁撼,同時對所使用的數據類型做出最小的假設歧胁。 Swift泛型創(chuàng)建的代碼沒有特定于底層數據類型,允許優(yōu)雅的抽象彤敛,產生更少的錯誤更清晰的代碼与帆。 它允許您編寫一次函數并在不同類型上使用它。

你會發(fā)現Swift中使用的泛型墨榄,這使得理解它們對完全掌握語言至關重要玄糟。 您將在Swift中遇到的通用示例是Optional類型。 您可以選擇任何您想要的數據類型袄秩,當然也包括您自己創(chuàng)建的類型阵翎。 換句話說,Optional數據類型在其可能包含的值類型上是通用的之剧。

在本教程中郭卫,您將在Swift playground中進行實驗以了解:

  • What exactly generics are
  • Why they are useful
  • How to write generic functions and data structures
  • How to use type constraints
  • How to extend generic types

首先要創(chuàng)建一個新的playground。 在Xcode中背稼,轉到File ? New ? Playground… 選擇macOS ? Blank模板贰军。 單擊Next并命名Playground為Generics。 最后蟹肘,單擊Create词疼!

作為居住在遙遠的王國的為數不多的程序員之一,你被召喚到皇家城堡幫助女王帘腹,這是一件非常重要的事情贰盗。 她已經忘記了自己擁有多少皇家科目,并且在計算方面需要一些幫助阳欲。

她要求編寫一個相加兩個整數的函數舵盈。 將以下內容添加到新創(chuàng)建的playground

func addInts(x: Int, y: Int) -> Int {
  return x + y
}

addInts(x:y :)獲取兩個Int值并返回它們的總和。 您可以通過將以下代碼添加到playground上來嘗試:

let intSum = addInts(x: 1, y: 2)

這是一個簡單的例子球化,展示了Swift的類型安全性秽晚。 您可以使用兩個整數調用此函數,但不能使用任何其他類型赊窥。

女王很高興爆惧,并立即要求另外一個add函數來計算她的財富 - 這個時候,添加Double值锨能。 創(chuàng)建第二個函數addDoubles(x:y :)

func addDoubles(x: Double, y: Double) -> Double {
  return x + y
}

let doubleSum = addDoubles(x: 1.0, y: 2.0)

addIntsaddDoubles的函數簽名是不同的扯再,但函數體是相同的芍耘。 你不僅有兩個函數,而且它們里面的代碼是重復的熄阻。 泛型Generics可用于將這兩個函數減少為一個并刪除冗余代碼斋竞。

但是,首先秃殉,您將了解日常Swift中常見編程的一些常見情況坝初。


Other Examples of Swift Generics

您可能沒有意識到,但您使用的一些最常見的結構钾军,例如數組鳄袍,字典,選項和結果都是泛型類型吏恭!

1. Arrays

將以下內容添加到您的playground

let numbers = [1, 2, 3]

let firstNumber = numbers[0]

在這里拗小,您創(chuàng)建一個包含三個數字的簡單數組,然后從該數組中取出第一個數字樱哼。

現在按住Option鍵單擊哀九,首先是numbers,然后是firstNumber搅幅。 你看到了什么阅束?

因為Swift具有類型推斷 type inference,所以您不必顯式定義常量的類型茄唐,但它們都具有確切的類型息裸。 numbers是一個[Int] - 也就是一個整數數組 - 而firstNumber是一個Int

Swift Array類型是泛型類型沪编。 通用類型都至少有一個類型參數界牡,一個尚未指定的其他類型的占位符。 您需要指定其他類型才能專門化泛型類型并實際創(chuàng)建它的實例漾抬。

例如,Array的type參數確定數組中的內容常遂。 您的數組是專用的纳令,因此它只能包含Int值。 這支持Swift的類型安全性克胳。 當你從那個數組中刪除任何東西時平绩,Swift - 更重要的是你 - 知道它必須是一個Int

通過向playground添加稍長版本的相同代碼漠另,您可以更好地了解Array的一般特性:

var numbersAgain: Array<Int> = []
numbersAgain.append(1)
numbersAgain.append(2)
numbersAgain.append(3)

let firstNumberAgain = numbersAgain[0]

通過Option-click來檢查numbersAgainfirstNumberAgain的類型捏雌;類型將與以前的值完全相同。 在這里笆搓,您可以使用顯式通用語法指定numbersAgain的類型性湿,方法是將Int放在Array之后的尖括號中纬傲。 您已將Int作為type參數的顯式類型參數提供。

嘗試在數組中添加其他內容肤频,如String

numbersAgain.append("All hail Lord Farquaad")

您將收到錯誤 - 類似于:Cannot convert value of type ‘String’ to expected argument type ‘Int’叹括。 編譯器告訴您不能將字符串添加到整數數組。 作為泛型類型Array的一種方法宵荒,append是一種所謂的泛型方法汁雷。 因為此數組實例是專用類型Array <Int>,所以它的append方法現在也專門用于append(_ newElement:Int)报咳。 它不會讓你添加一些不正確的類型侠讯。

刪除導致錯誤的行。 接下來暑刃,您將看到標準庫中另一個泛型示例厢漩。

2. Dictionaries

字典也是泛型類型,并導致類型安全的數據結構稍走。

playground末尾創(chuàng)建以下魔法王國詞典袁翁,然后查找Freedonia的國家代碼:

let countryCodes = ["Arendelle": "AR", "Genovia": "GN", "Freedonia": "FD"]
let countryCode = countryCodes["Freedonia"]

檢查兩個聲明的類型。 您將看到countryCodesString鍵和String值的字典 - 此字典中沒有任何其他內容婿脸。 形式泛型類型是Dictionary粱胜。

3. Optionals

在上面的示例中,請注意countryCode的類型是String狐树?焙压。 這實際上只是Optional的簡寫。

如果<and>看起來很熟悉抑钟,那是因為甚至Optional是泛型類型涯曲。 泛型到處都是!

這里編譯器強制您只能使用字符串鍵訪問字典在塔,并且始終返回返回的字符串值幻件。 可選類型用于表示countryCode,因為可能沒有與該鍵對應的值蛔溃。 例如绰沥,如果您嘗試查找The Emerald City,則countryCode的值將為nil贺待,因為它不存在于您的魔法王國詞典中徽曲。

將以下內容添加到您的playground,以查看創(chuàng)建可選字符串的完整顯式語法:

let optionalName = Optional<String>.some("Princess Moana")
if let name = optionalName {}

檢查name的類型麸塞,您將看到它是String秃臣。

Optional binding 可選綁定,即if-let構造哪工,是對各種類型的泛型轉換奥此。 它需要T?類型的通用值弧哎,并為您提供類型為T的通用值。這意味著您可以使用if let到任何具體類型得院。

It’s T time!

4. Results

ResultSwift 5中的一個新類型傻铣。與Optional一樣,它是一個包含兩個case的通用枚舉祥绞。 結果要么取而代之非洲,要么success,要么failure蜕径。 每個case都有自己的關聯(lián)泛型類型两踏,success有一個值,failure有一個Error兜喻。

考慮這種情況梦染,皇家魔法師招募你施放一些法術。 已知法術會生成一個符號朴皆,但未知法術會失敗帕识。 該函數看起來像這樣:

enum MagicError: Error {
  case spellFailure
}

func cast(_ spell: String) -> Result<String, MagicError> {
  switch spell {
  case "flowers":
    return .success("??")
  case "stars":
    return .success("?")
  default:
    return .failure(.spellFailure)
  }
}

Result允許您編寫返回值或錯誤的函數,而無需使用try語法遂铡。 作為額外的好處肮疗,failure case的通用規(guī)范意味著您不需要像使用catch塊那樣檢查類型。 如果出現錯誤扒接,您可以確定與.failure case案例相關聯(lián)的值中會出現MagicError伪货。

試試一些法術來看看Result

let result1 = cast("flowers") // .success("??")
let result2 = cast("avada kedavra") // .failure(.spellFailure)

掌握了泛型的基礎知識,您可以學習如何編寫自己的通用數據結構和函數钾怔。


Writing a Generic Data Structure

隊列queue是一種類似于列表或堆棧的數據結構碱呼,但是您只能向其添加新值(將它們排入隊列)并且只從前面獲取值(將它們出列)。 如果您曾經使用過OperationQueue宗侦,那么這個概念可能會很熟悉愚臀。

女王,對你在本教程前面的努力感到高興矾利,現在希望你能夠編寫函數懊悯,以幫助跟蹤等待與她交談的皇室成員。

將以下struct聲明添加到playground的末尾:

struct Queue<Element> {
}

Queue是泛型類型梦皮,在其泛型參數子句中具有類型參數Element。 另一種說法是桃焕,Queue在類型Element上是通用的剑肯。 例如,Queue <Int>Queue <String>將在運行時成為它們自己的具體類型观堂,它們只能分別對字符串和整數進行入隊和出列让网。

將以下屬性添加到隊列中:

private var elements: [Element] = []

您將使用此數組來保存元素呀忧,您將其初始化為空數組。 請注意溃睹,您可以使用Element而账,就好像它是一個真實的類型,即使它稍后會被填充因篇。 您將其標記為private泞辐,因為您不希望Queue的使用者訪問elements。 您希望強制它們使用方法來訪問后備存儲竞滓。

最后咐吼,實現兩個主要的隊列方法:

mutating func enqueue(newElement: Element) {
  elements.append(newElement)
}

mutating func dequeue() -> Element? {
  guard !elements.isEmpty else { return nil }
  return elements.remove(at: 0)
}

同樣,類型參數Elementstruct中的任何位置都可用商佑,包括內部方法锯茄。 使類型通用就像使其每個方法在同一類型上隱式通用。 您已經實現了類型安全的通用數據結構茶没,就像標準庫中的那樣肌幽。

playground底部玩一下你的新數據結構,通過將他們的royal id添加到隊列中來排隊等待主題:

var q = Queue<Int>()

q.enqueue(newElement: 4)
q.enqueue(newElement: 2)

q.dequeue()
q.dequeue()
q.dequeue()
q.dequeue()

通過故意制造盡可能多的錯誤來觸發(fā)與泛型相關的不同錯誤消息抓半,從而獲得一些樂趣 - 例如喂急,在隊列中添加一個字符串。 您現在對這些錯誤了解得越多琅关,就越容易在更復雜的項目中識別和處理它們煮岁。


Writing a Generic Function

女王有很多要處理的數據,她要求你編寫的下一段代碼將采用鍵和值的字典并將其轉換為列表涣易。

將以下函數添加到playground的底部:

func pairs<Key, Value>(from dictionary: [Key: Value]) -> [(Key, Value)] {
  return Array(dictionary)
}

仔細看看函數聲明画机,參數列表和返回類型。

該函數對于您已命名為KeyValue的兩種類型是通用的新症。 唯一的參數是具有KeyValue類型的鍵值對的字典步氏。 返回值是表單元組的數組 - 你猜對了 - (Key,Value)徒爹。

您可以在任何有效字典上使用pairs(from:)荚醒,并且它將起作用,這要歸功于泛型:

let somePairs = pairs(from: ["minimum": 199, "maximum": 299])
// result is [("maximum", 299), ("minimum", 199)]

let morePairs = pairs(from: [1: "Swift", 2: "Generics", 3: "Rule"])
// result is [(1, "Swift"), (2, "Generics"), (3, "Rule")]

當然隆嗅,由于你無法控制字典項進入數組的順序界阁,你可能會在playground上看到一個元組值的順序更像“Generics”, “Rule”, “Swift”,實際上胖喳,它們那樣做泡躯!

在運行時,每個可能的KeyValue將作為一個單獨的函數,填充函數聲明和正文中的具體類型较剃。對pair(from :)的第一次調用返回一個(String咕别,Int)元組數組。第二個調用在元組中使用翻轉的類型順序写穴,并返回(Int惰拱,String)元組的數組。

您創(chuàng)建了一個可以使用不同的調用返回不同類型的函數啊送。您可以看到如何將邏輯保存在一個位置可以簡化代碼偿短。您需要使用一個函數處理兩個調用,而不需要兩個不同的函數删掀。

現在您已經了解了創(chuàng)建和使用泛型類型和函數的基礎知識翔冀,現在是時候繼續(xù)使用一些更高級的功能了。您已經看到了泛型如何按類型限制事物披泪,但您可以添加其他約束以及擴展泛型類型以使它們更有用纤子。


Constraining a Generic Type

為了分析一小部分她最忠誠的主題的年齡,女王要求一個函數來排序數組并找到中間值款票。

將以下函數添加到playground時:

func mid<T>(array: [T]) -> T? {
  guard !array.isEmpty else { return nil }
  return array.sorted()[(array.count - 1) / 2]
}

你會收到一個錯誤控硼。 問題是要使sorted()工作,數組的元素必須是Comparable艾少。 只要元素類型實現Comparable卡乾,你需要以某種方式告訴Swift mid可以接受任何數組。

將函數聲明更改為以下內容:

func mid<T: Comparable>(array: [T]) -> T? {
  guard !array.isEmpty else { return nil }
  return array.sorted()[(array.count - 1) / 2]
}

在這里缚够,您使用語法將類型約束添加到泛型類型參數T幔妨。您現在只能使用Comparable元素數組調用該函數,以便sorted()始終有效谍椅! 通過添加以下內容來嘗試約束函數:

mid(array: [3, 5, 1, 2, 4]) // 3

您在使用Result時已經看到過這種情況:Failure類型被限制為Error误堡。

1. Cleaning Up the Add Functions

現在您已了解類型約束,您可以從playground的開頭創(chuàng)建add函數的通用版本 - 這將更加優(yōu)雅雏吭,并且請女王們非常高興锁施。 將以下協(xié)議和擴展添加到您的playground

protocol Summable { static func +(lhs: Self, rhs: Self) -> Self }
extension Int: Summable {}
extension Double: Summable {}

首先,您創(chuàng)建一個Summable協(xié)議杖们,該協(xié)議表明任何符合的類型都必須具有加法運算符+悉抵。 然后,指定IntDouble類型符合它摘完。

現在使用泛型參數T和類型約束姥饰,您可以創(chuàng)建一個通用函數add

func add<T: Summable>(x: T, y: T) -> T {
  return x + y
}

您已將兩個函數(實際上更多,因為您需要更多其他Summable類型)減少到一個并刪除冗余代碼孝治。 您可以在整數和雙精度上使用新函數:

let addIntSum = add(x: 1, y: 2) // 3
let addDoubleSum = add(x: 1.0, y: 2.0) // 3.0

您還可以在其他類型上使用它列粪,例如字符串

extension String: Summable {}
let addString = add(x: "Generics", y: " are Awesome!!! :]")

通過向Summable添加其他符合要求的類型栅螟,您的add(x:y :)函數由于其泛型驅動的定義而變得更加廣泛有用!


Extending a Generic Type

一個Court Jester一直在協(xié)助女王守護等待的皇家臣民篱竭,讓女王知道下一個主題,然后才正式問候步绸。 他透過她客廳的窗戶偷看掺逼。 您可以使用擴展來對其行為進行建模,該擴展應用于本教程前面的通用隊列類型瓤介。

擴展Queue類型并在Queue定義的正下方添加以下方法:

extension Queue {
  func peek() -> Element? {
    return elements.first
  }
}

peek返回第一個元素而不將其出列吕喘。 擴展泛型類型很容易! 泛型類型參數與原始定義的主體一樣可見刑桑。 您可以使用擴展程序查看隊列:

q.enqueue(newElement: 5)
q.enqueue(newElement: 3)
q.peek() // 5

您將看到值5作為隊列中的第一個元素氯质,但沒有任何內容出列,并且隊列具有與以前相同數量的元素祠斧。

Royal Challenge:擴展Queue類型以實現一個函數isHomogeneous闻察,它檢查隊列的所有元素是否相等。 您需要在Queue聲明中添加類型約束琢锋,以確痹可以檢查其元素是否相互相等。

Answer:

首先編輯Queue的定義吴超,以便Element符合Equatable協(xié)議:

struct Queue<Element: Equatable> {

然后compose是你playground底部的Homogeneous()

extension Queue {
 func isHomogeneous() -> Bool {
   guard let first = elements.first else { return true }
   return !elements.contains { $0 != first }
 }
}

最后钉嘹,測試結果:

var h = Queue<Int>()
h.enqueue(newElement: 4)
h.enqueue(newElement: 4)
h.isHomogeneous() // true
h.enqueue(newElement: 2)
h.isHomogeneous() // false

Subclassing a Generic Type

Swift具有子類泛型類的能力。 在某些情況下鲸阻,這可能很有用跋涣,例如創(chuàng)建泛型類的具體子類。

將以下泛型類添加到playground中:

class Box<T> {
  // Just a plain old box.
}

在這里定義一個Box類鸟悴。 該box可以包含任何內容陈辱,這就是為什么它是泛型類。 您可以通過兩種方式將Box子類化:

  • 1) 你可能想要擴展box的功能以及它是如何工作的遣臼,但要保持通用性性置,所以你仍然可以在box里放任何東西;
  • 2) 您可能希望擁有一個專門的子類,它始終知道其中的內容揍堰。

Swift允許兩者鹏浅。 將其添加到您的playground

class Gift<T>: Box<T> {
  // By default, a gift box is wrapped with plain white paper
  func wrap() {
    print("Wrap with plain white paper.")
  }
}

class Rose {
  // Flower of choice for fairytale dramas
}

class ValentinesBox: Gift<Rose> {
  // A rose for your valentine
}

class Shoe {
  // Just regular footwear
}

class GlassSlipper: Shoe {
  // A single shoe, destined for a princess
}

class ShoeBox: Box<Shoe> {
  // A box that can contain shoes
}

你在這里定義了兩個Box子類:GiftShoeBoxGift是一種特殊的Box屏歹,它是分開的隐砸,因此你可以在其上定義不同的方法和屬性,例如wrap()蝙眶。 但是季希,它在類型上仍然具有通用性褪那,這意味著它可以包含任何內容。 ShoeGlassSlipper是一種非常特殊的鞋子式塌,已經被聲明博敬,可以放在ShoeBox的一個實例中進行交付。

在子類聲明下聲明每個類的實例:

let box = Box<Rose>() // A regular box that can contain a rose
let gift = Gift<Rose>() // A gift box that can contain a rose
let shoeBox = ShoeBox()

請注意峰尝,ShoeBox初始化程序不再需要采用泛型類型參數偏窝,因為它已在ShoeBox的聲明中修復。

接下來武学,聲明子類ValentinesBox的一個新實例 - 一個包含玫瑰的盒子祭往,一個專門用于情人節(jié)的神奇禮物。

let valentines = ValentinesBox()

雖然標準盒子用白紙包裹火窒,但你希望你的節(jié)日禮物有點發(fā)燒友硼补。 將以下方法添加到ValentinesBox

override func wrap() {
  print("Wrap with ??? paper.")
}

最后,通過將以下代碼添加到您的playground來比較包裝這兩種類型的結果:

gift.wrap() // plain white paper
valentines.wrap() // ??? paper

ValentinesBox雖然使用泛型構造熏矿,但它作為標準子類運行已骇,其方法可以從超類繼承和覆蓋。 多么優(yōu)雅曲掰!


Enumerations With Associated Values

女王對您的工作很滿意疾捍,并希望為您提供獎勵:您選擇的通用寶藏或獎章。

將以下聲明添加到playground的末尾:

enum Reward<T> {
  case treasureChest(T)
  case medal

  var message: String {
    switch self {
    case .treasureChest(let treasure):
      return "You got a chest filled with \(treasure)."
    case .medal:
      return "Stand proud, you earned a medal!"
    }
  }
}

此語法允許您編寫枚舉栏妖,其中至少有一個case是通用box乱豆。 使用message var,您可以將值恢復吊趾。 在上面說明的Result示例中宛裕,成功和失敗案例都是通用的,具有不同的類型论泛。

要恢復關聯(lián)值揩尸,請使用如下:

let message = Reward.treasureChest("??").message
print(message)

Swift泛型是許多常見語言功能的核心,例如數組和可選項屁奏。 您已經了解了如何使用它們來構建優(yōu)雅岩榆,可重用的代碼,從而減少錯誤坟瓢。

有關更多信息勇边,請閱讀Apple指南Swift編程語言的泛型Generics章節(jié)和通用參數和參數語言 Generic Parameters and Arguments參考章節(jié)。 您將在Swift中找到有關泛型的更多詳細信息折联,以及一些方便的示例粒褒。

Swift中的泛型是一個不可或缺的功能,您每天都會使用它來編寫功能強大且類型安全的抽象诚镰。

后記

本篇主要講述了Swift泛型相關奕坟,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末祥款,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子月杉,更是在濱河造成了極大的恐慌刃跛,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苛萎,死亡現場離奇詭異奠伪,居然都是意外死亡,警方通過查閱死者的電腦和手機首懈,發(fā)現死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谨敛,“玉大人究履,你說我怎么就攤上這事×忱辏” “怎么了最仑?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炊甲。 經常有香客問我泥彤,道長,這世上最難降的妖魔是什么卿啡? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任吟吝,我火速辦了婚禮,結果婚禮上颈娜,老公的妹妹穿的比我還像新娘剑逃。我一直安慰自己,他們只是感情好官辽,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布蛹磺。 她就那樣靜靜地躺著,像睡著了一般同仆。 火紅的嫁衣襯著肌膚如雪萤捆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天俗批,我揣著相機與錄音俗或,去河邊找鬼。 笑死扶镀,一個胖子當著我的面吹牛蕴侣,可吹牛的內容都是我干的。 我是一名探鬼主播臭觉,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼昆雀,長吁一口氣:“原來是場噩夢啊……” “哼辱志!你這毒婦竟也來了?” 一聲冷哼從身側響起狞膘,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤揩懒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挽封,有當地人在樹林里發(fā)現了一具尸體已球,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年辅愿,在試婚紗的時候發(fā)現自己被綠了智亮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡点待,死狀恐怖阔蛉,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情癞埠,我是刑警寧澤状原,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站苗踪,受9級特大地震影響颠区,放射性物質發(fā)生泄漏。R本人自食惡果不足惜通铲,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一毕莱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颅夺,春花似錦央串、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稚字,卻和暖如春饲宿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胆描。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工瘫想, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昌讲。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓国夜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親短绸。 傳聞我的和親對象是個殘疾皇子车吹,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容