Swift5.0 - day6-錯誤處理、泛型、高級運算符

一、錯誤處理

  • 1.1、錯誤類型

    • 語法錯誤(編譯報錯)
    • 邏輯錯誤
    • 運行時錯誤(可能會導致閃退,一般也叫做異常)
  • 1.2、自定義錯誤

    • Swift中可以通過 Error 協議自定義運行時的錯誤信息

      enum SomeError : Error {
          case illegalArg(String) 
          case outOfBounds(Int, Int) 
          case outOfMemory
      }
      
    • 函數內部通過 throw 拋出自定義 Error,可能會拋出 Error 的函數必須加上throws聲明

      func divide(_ num1: Int, _ num2: Int) throws -> Int { 
          if num2 == 0 {
               throw SomeError.illegalArg("0不能作為除數")
          }
          return num1 / num2
      }
      
    • 需要使用 try 調用可能會拋出 Error 的函數

      do {
         let result = try divide(10, 0)
         print("result=\(result)")
      } catch {
         print(error)
      }
      
  • 1.3、do-catch 捕獲錯誤
    可以使用do-catch捕捉Error

    func test() {
         print("1")
         do {
            print("2")
            print(try divide(20, 0))
            print("3")
         } catch let SomeError.illegalArg(msg) {
            print("參數異常:", msg)
         } catch let SomeError.outOfBounds(size, index) {
            print("下標越界:", "size=\(size)", "index=\(index)")
         } catch SomeError.outOfMemory {
            print("內存溢出") } catch {
            print("其他錯誤") }
            print("4")
    }
    
    • 拋出Error后,try下一句直到作用域結束的代碼都將停止運行

      test()
      // 1
      // 2
      // 參數異常: 0不能作為除數
      // 4
      
      do {
          try divide(20, 0)
      } catch let error {
          switch error {
          case let SomeError.illegalArg(msg): 
               print("參數錯誤:", msg)
          default: 
               print("其他錯誤")
          } 
      }
      
  • 1.4、處理Error
    處理Error的2種方式

    • 通過do-catch捕捉Error

      func test() throws {
           print("1")
           do {
              print("2")
              print(try divide(20, 0))
              print("3")
           } catch let error as SomeError {
              print(error)
           }
           print("4") 
      }
      try test()
      // 1
      // 2
      // illegalArg("0不能作為除數") 
      // 4
      
    • 不捕捉Error,在當前函數增加throws聲明,Error將自動拋給上層函數;如果最頂層函數(main函數)依然沒有捕捉Error,那么程序將終止

      func test() throws {
         print("1")
         print(try divide(20, 0))
         print("2") 
      }
      try test()
      // 1
      // Fatal error: Error raised at top level
      
      do {
          print(try divide(20, 0))
      } catch is SomeError {
          print("SomeError")
      }
      

      提示:try 僅僅代表嘗試去調用一個函數

  • 1.5、try?、try!

    • 可以使用try?、try!調用可能會拋出Error的函數,這樣就不用去處理Error

      func test() {
         print("1")
         var result1 = try? divide(20, 10) // Optional(2), Int?
         var result2 = try? divide(20, 0) // nil
         var result3 = try! divide(20, 10) // 2, Int
         print("2")
      } 
      test()
      
    • 下面 a、b 是等價的

      var a = try? divide(20, 0)
      var b: Int?
      do {
          b = try divide(20, 0)
      } catch { 
         b = nil 
      }
      

      提示:b = try divide(20, 0) 在 try 后面拋出錯區后,就不會再給 b 賦值,直接走 catch

  • 1.6、rethrows
    rethrows 表明:函數本身不會拋出錯誤,但調用閉包參數拋出錯誤,那么它會將錯誤向上拋

    func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows 
    {
        print(try fn(num1, num2))
    }
    // Fatal error: Error raised at top level
    try exec(divide, 20, 0)
    

    提示:rethrows 是一個聲明,上述的 rethrows 代表的是上面拋出的 錯誤不是 exec 函數 本身,而是 函數內部的 fn 函數 拋出的錯誤

    • 總之:rethrows 還是 throws 區別 如下
    • 共同點:都代表往外拋出錯誤
    • 不同點:throws 代表不是通過傳遞進來的參數調用導致的異常,是函數內部寫其他代碼導致的異常;rethrows:是因為傳進來的參數調用導致的異常
  • 1.7、defer

    • defer 語句:用來定義以任何方式(拋錯誤、return等)離開代碼前必須要執行的代碼

    • defer 語句將延遲至當前作用域結束之前執行

      func open(_ filename: String) -> Int { 
          print("open")
          return 0
      }
      
      func close(_ file: Int) {
          print("close")
      }
      
      func processFile(_ filename: String) throws {
          let file = open(filename)
          defer {
              close(file)
          }
          // 使用file
          // ....
          try divide(20, 0)
          // close將會在這里調用 
      }
      try processFile("test.txt")
      // open
      // close
      // Fatal error: Error raised at top level
      
    • defer 語句的 執行順序定義順序 相反

      func fn1() { 
          print("fn1")
      }
      func fn2() { 
          print("fn2") 
      }
      func test() {
           defer { fn1() }
           defer { fn2() }
      }
      
      test()
      // 打印結果如下
      // fn2
      // fn1
      

      總結:defer

      • 語句的 執行順序定義順序 相反
      • 當前作用域結束之前執行
  • 1.8、assert (斷言)

    • 很多編程語言都有斷言機制:不符合指定條件就拋出運行時錯誤,常用于調試(Debug)階段的條件判斷

    • 默認情況下,Swift的斷言只會在Debug模式下生效,Release模式下會忽略

      func divide(_ v1: Int, _ v2: Int) -> Int { 
           assert(v2 != 0, "除數不能為0")
           return v1 / v2
      }
      print(divide(20, 0))
      
    • 增加Swift Flags修改斷言的默認行為
      -assert-config Release:強制關閉斷言
      -assert-config Debug:強制開啟斷言

      增加Swift Flags修改斷言的默認行為
  • 1.9、fatalError

    • 如果遇到嚴重問題,希望結束程序運行時,可以直接使用fatalError函數拋出錯誤(這是無法通過do-catch捕捉的錯誤)

    • 使用了fatalError函數,就不需要再寫return

      func test(_ num: Int) -> Int {
          if num >= 0 {
              return 1
          }
          fatalError("num不能小于0")
      }
      
    • 在某些不得不實現、但不希望別人調用的方法,可以考慮內部使用fatalError函數

      class Person { required init() {} } 
      class Student : Person {
          required init() { 
              fatalError("don't call Student.init")
          }
          init(score: Int) {}
      }
      var stu1 = Student(score: 98) 
      var stu2 = Student()
      
  • 1.10、局部作用:可以使用 do 實現局部作用域

    do {
        let dog1 = Dog()
        dog1.age = 10
        dog1.run() 
    }
    
    do {
        let dog2 = Dog()
        dog2.age = 10
        dog2.run()
    }
    

二、泛型 (Generics)

  • 2.1、泛型可以將類型參數化,提高代碼復用率,減少代碼量

    func swapValues<T>(_ a: inout T, _ b: inout T) { 
         (a, b) = (b, a)
    }
    var x1 = 10
    var x2 = 20
    swapValues(&x1, &x2)
    var d1 = 10.0
    var d2 = 20.0
    swapValues(&d1, &d2)
    
    struct Date {
        var year = 0, month = 0, day = 0
    }
    var dd1 = Date(year: 2011, month: 9, day: 10) 
    var dd2 = Date(year: 2012, month: 10, day: 11) 
    swapValues(&dd1, &dd2)
    

    提示 :T 僅僅是參數的泛型名字,泛型名字定義要有意義,可以根據自己的需要定義為其他的名字

  • 2.2、泛型函數賦值給變量, T1 和 T2 僅僅代表函數 的兩個參數的類型不同

    func test<T1, T2>(_ t1: T1, _ t2: T2) {} 
    var fn1: (Int, Double) -> () = test
    var fn2: (String, Double) -> () = test
    
  • 2.3、泛型使用例子

    // 父類
    class Stack<E> {
        var elements = [E]()
        init(firstElement: E){
           elements.append(firstElement)
        }
        func push(_ element: E) { 
           elements.append(element)
        } 
        func pop() -> E { 
           elements.removeLast()
        }
        func top() -> E { 
           elements.last! 
        }
        func size() -> Int { 
           elements.count
        }
    }
    // 子類
    class SubStack<E> : Stack<E> {}
    
    var stack = Stack<Int>()
    stack.push(11)
    stack.push(22)
    stack.push(33)
    print(stack.top()) // 33
    print(stack.pop()) // 33
    print(stack.pop()) // 22
    print(stack.pop()) // 11
    print(stack.size()) // 0
    
    struct Stack<E> {
        var elements = [E]()
        mutating func push(_ element: E) { 
            elements.append(element) 
        } 
        mutating func pop() -> E { 
            elements.removeLast() 
        }
        func top() -> E { 
            elements.last! 
        }
        func size() -> Int {
            elements.count 
        }
    }
    

    提示:在結構體的泛型里面,在函數里面修改變量,要加上 mutating

    enum Score<T> {
         case point(T)
         case grade(String)
    }
    
    let score0 = Score<Int>.point(100) 
    let score1 = Score.point(99)
    let score2 = Score.point(99.5)
    let score3 = Score<Int>.grade("A")
    
  • 2.4、關聯類型

    • 關聯類型的作用:給協議中用到的類型定義一個占位名稱

    • 協議中可以擁有多個關聯類型

      protocol Stackable {
          // 協議中可以擁有多個關聯類型
          associatedtype Element // 關聯類型 1
          associatedtype Element2 // 關聯類型 2
          mutating func push(_ element: Element) 
          mutating func pop() -> Element
          func top() -> Element
          func size() -> Int
      }
      

      提示: associatedtype Element 關聯類型 這是 swift的規定,而不能像函數、類、結構體 - > <泛型名字>

      明確關聯的類型, typealias Element = String,也可以省略,因為 func push(_ element: String) 函數可以識別類型

      class StringStack : Stackable {
          // 給關聯類型設定真實類型
          // typealias Element = String
          var elements = [String]()
          func push(_ element: String) {
              elements.append(element) 
          } 
          func pop() -> String { 
              elements.removeLast() 
          }
          func top() -> String {
              elements.last! 
          }
          func size() -> Int {
              elements.count 
          }
      }
      
      var ss = StringStack()
      ss.push("Jack")
      ss.push("Rose")
      

      類設置泛型,可以如下定義,同時 typealias Element = E 可以省略

      class Stack<E> : Stackable {
           // typealias Element = E
           var elements = [E]()
           func push(_ element: E) {
               elements.append(element) 
           }
           func pop() -> E { 
               elements.removeLast() 
           } func top() -> E { 
               elements.last! 
           }
           func size() -> Int { 
               elements.count 
           }
      }
      
  • 2.5、對泛型進行約束

    • 通過 繼承基類 或者 遵守協議 和 通過 where 語句來 進行約束

      protocol Runnable { }
      class Person { }
      func swapValues<T : Person & Runnable>(_ a: inout T, _ b: inout T) {
              (a, b) = (b, a)
      }
      protocol Stackable {
          associatedtype Element: Equatable
      }
      class Stack<E : Equatable> : Stackable { typealias Element = E }
            func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
             return false
      }
      var stack1 = Stack<Int>()
      var stack2 = Stack<String>()
      // error: requires the types 'Int' and 'String' be equivalent equal(stack1, stack2)
      

      提示:傳進來的參數泛型要是 Person 類型并且遵守Runnable協議,還有 where 判斷

  • 2.6、協議類型的注意點

    protocol Runnable {
        associatedtype Speed
        var speed: Speed { get }
    }
    class Person : Runnable {
        var speed: Double { 0.0 }
    }
    class Car : Runnable {
        var speed: Int { 0 }
    }
    func get(_ type: Int) -> Runnable {
        if type == 0 {
            return Person()
        }
        return Car()
    }
    
    var r1 = get(0)
    var r2 = get(1)
    

    提示: func get(_ type: Int) -> Runnable 會報錯:Protocol 'Runnable8' can only be used as a generic constraint because it has Self or associated type requirements

    • 原因:Runnable 協議里面 associatedtype 關聯類型

    • 解決辦法1:使用泛型

      func get<T: Runnable>(_ type: Int) -> T {
           if type == 0 {
               return Person() as! T
           }
           return Car() as! T
      }
      var r1: Person = get(0)
      var r2: Car = get(1)
      
    • 解決辦法2:使用 some 關鍵字聲明一個不透明類型

      func get(_ type: Int) -> some Runnable {
           return Car()
      }
      var r1 = get(0)
      var r2 = get(1)
      
  • 2.7、不透明類型

    • some 限制只能返回一種類型

      func get(_ type: Int) -> some Runnable {
          if type == 0 {
              return Person()
          }
          return Car()
      }
      

      提示:報錯 Function declares an opaque return type, but the return statements in its body do not have matching underlying types

    • some 除了用在返回值類型上,一般還可以用在屬性類型上

      protocol Runnable {
         associatedtype Speed
      }
      class Dog : Runnable {
           typealias Speed = Double   
      }
      class Person {
           var pet: some Runnable {
              return Dog()
           }
      }
      var person = Person10()
      person.pet
      

      提示:我們可以看到返回的是遵守了 Runnable 協議的對象


  • 2.8、可選項的本質是 enum 類型

    public enum Optional<Wrapped> : ExpressibleByNilLiteral { 
         case none
         case some(Wrapped)
         public init(_ some: Wrapped) 
    }
    var age: Int? = .none
    age = 10
    age = .some(20)
    age = nil
    var age: Int? = 10
    var age0: Optional<Int> = Optional<Int>.some(10) 
    var age1: Optional = .some(10)
    var age2 = Optional.some(10)
    var age3 = Optional(10)
    age = nil
    age3 = .none
    
    switch age {
         case let v?:
             print("some", v)
         case nil:
             print("none")
    }
    switch age {
         case let .some(v):
             print("some", v)
         case .none:
             print("none")
    }
    var age: Int? = nil
    var age0 = Optional<Int>.none 
    var age1: Optional<Int> = .none
    

    多重可選項

    var age_: Int? = 10
    var age: Int?? = age_
    age = nil
    
    var age0 = Optional.some(Optional.some(10)) 
    age0 = .none
    var age1: Optional<Optional> = .some(.some(10))
    age1 = .none
    
    var age: Int?? = 10  // 等價于下面的  
    var age0: Optional<Optional> = 10
    

三、高級運算符

  • 3.1、溢出運算符

    • Swift的算術運算符出現溢出時還會拋出運行時錯誤

    • Swift有溢出運算符(&+、&-、&*),用來支持溢出運算

    • Int8(有符號): -128 ~ 127;UInt8(無符號):0 ~ 255

    • 在使用了溢出運算符,那么范圍就變成了一個圈,如下結果:

      print(Int8.max &+ 1)  // 結果是   128  &+ 1 = -128
      print(Int8.min &- 1)  // 結果是   -128  &- 1 = 127
      

    提示:使用了溢出運算符,那么范圍就變成了一個圈 如:UInt8(-128~127~128~127)

  • 3.2、運算符重載
    類、結構體 、枚舉 可以為現有的運算符提供自定義的實現,這個操作叫做:運算符重載

    class Point {
        var x: Int
        var y: Int
        init(x: Int,y:Int) {
            self.x = x
            self.y = y
        }
        static func + (p1: Point,p2: Point) -> Point {
            Point(x: p1.x + p2.x, y: p1.y + p2.y)
        }
        static func - (p1: Point,p2: Point) -> Point {
            Point(x: p1.x - p2.x, y: p1.y - p2.y)
        }
        static func += (p1: inout Point, p2: Point) {
            p1 = p1 + p2
        }
        static func -= (p1: inout Point, p2: Point) {
            p1 = p1 - p2
        }
        static prefix func ++ (p: inout Point) -> Point {
            p += Point(x: 1, y: 1)
            return p
        }
        static postfix func ++ (p: inout Point) -> Point {
            let tmp = p
            p += Point(x: 1, y: 1)
            return tmp
        }
        static func == (p1: Point, p2: Point) -> Bool {
            (p1.x == p2.x) && (p1.y == p2.y)
        }
    }
    
    let p1 = Point(x: 1, y: 2)
    let p2 = Point(x: 10, y:20)
    
    let p3 = p1 + p2
    print(p3.x,p3.y)
    

    提示:prefix代表前置,postfix代表后置

  • 3.3、Equatable

    • 要想得知2個實例是否等價,一般做法是遵守 Equatable 協議,重載 == 運算符;與此同時,等價于重載了 != 運算符

      class Person: Equatable {
          var age: Int
          init(age: Int) {
              self.age = age
          }
          static func == (lhs: Person, rhs: Person) -> Bool {
              lhs.age == rhs.age
          }
      }
      
    • Swift為以下類型提供默認的Equatable 實現

      • 沒有關聯類型的枚舉

        enum Car {
           case Name
           case Price
        }
        let car1 = Car.Name
        let car2 = Car.Price
        print(car1 == car2)
        // false
        
      • 只擁有遵守 Equatable 協議關聯類型的枚舉

        enum Answer {
            case wrong(Int,String)
            case right
        }
        

        提示:Int,String......都是遵守 Equatable 的

      • 只擁有遵守 Equatable 協議存儲屬性的結構體

        struct Point : Equatable {
            var x: Int, y: Int
        }
        var p1 = Point(x: 10, y: 20)
        var p2 = Point(x: 11, y: 22)
        print(p1 == p2) // false
        print(p1 != p2) // true
        
    • 引用類型比較存儲的地址值是否相等(是否引用著同一個對象),使用恒等運算符 ===!==

      提示:===!== 用于引用類型,比較指向的內存地址是否是同一個

  • 3.4、Comparable

    • score大的比較大,若score相等,age小的比較大

      struct Student : Comparable {
           var age: Int
           var score: Int
           init(score: Int, age: Int) {
               self.score = score
               self.age = age
           }
           static func < (lhs: Student, rhs: Student) -> Bool { 
            (lhs.score < rhs.score) || (lhs.score == rhs.score && lhs.age > rhs.age)
           }
           static func > (lhs: Student, rhs: Student) -> Bool {
               (lhs.score > rhs.score) || (lhs.score == rhs.score && lhs.age < rhs.age)
           }
           static func <= (lhs: Student, rhs: Student) -> Bool {
              !(lhs > rhs) 
           }
           static func >= (lhs: Student, rhs: Student) -> Bool {
              !(lhs < rhs)
           } 
      }
      
    • 要想比較2個實例的大小,一般做法是:

      • (1)、遵守 Comparable 協議

      • (2)、重載相應的運算符

        var stu1 = Student(score: 100, age: 20) 
        var stu2 = Student(score: 98, age: 18) 
        var stu3 = Student(score: 100, age: 20) 
        print(stu1 > stu2) // true
        print(stu1 >= stu2) // true
        print(stu1 >= stu3) // true
        print(stu1 <= stu3) // true
        print(stu2 < stu1) // true
        print(stu2 <= stu1) // true
        
  • 3.5、自定義運算符

    • 可以自定義新的運算符:在全局作用域使用 operator 進行聲明

      prefix operator 前綴運算符
      postfix operator 后綴運算符
      infix operator 中綴運算符 : 優先級組

      prefix operator +++
      prefix func +++ (_ i: inout Int) {
            i +=  2
      }
      var  age  = 3
      print(+++age)
      

      優先級組

      precedencegroup 優先級組 {
         associativity: 結合性(left\right\none)
         higherThan: 比誰的優先級高
         lowerThan: 比誰的優先級低
         assignment: true代表在可選鏈操作中擁有跟賦值運算符一樣的優先級
      }
      
      prefix operator +++
      infix operator +- : PlusMinusPrecedence 
      precedencegroup PlusMinusPrecedence {
          associativity: none
          higherThan: AdditionPrecedence 
          lowerThan: MultiplicationPrecedence 
          assignment: true
      }
      

      提示:

      • associativity: 結合性(left\right\none)
      • higherThan: 比誰的優先級高
      • lowerThan: 比誰的優先級低
      • assignment: true代表在可選鏈操作中擁有跟賦值運算符一樣的優先級

      Apple文檔:運算符參考

    • 自定義 運算符實例

      prefix operator +++
      infix operator +- : PlusMinusPrecedence 
      precedencegroup PlusMinusPrecedence {
             associativity: none
             higherThan: AdditionPrecedence 
             lowerThan: MultiplicationPrecedence 
             assignment: true
      }
      struct Point {
          var x: Int, y: Int
          static prefix func +++ (point: inout Point) -> Point {
              point = Point(x: point.x + point.x, y: point.y + point.y)
              return point
          }
          static func +- (left: Point, right: Point) -> Point { 
              return Point(x: left.x + right.x, y: left.y - right.y)
          }
          static func +- (left: Point?, right: Point) -> Point {
              print("+-")
              return Point(x: left?.x ?? 0 + right.x, y: left?.y ?? 0 - right.y) 
          }
      }
      struct Person {
         var point: Point
      }
      var person: Person? = nil 
      person?.point +- Point(x: 10, y: 20)
      

      提示:person?.point +- Point(x: 10, y: 20)+- 等同于 =

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容