Swift5.0 - day4-閉包、屬性、方法、下標

一、閉包

  • 1.1、閉包表達式(Closure Expression)
    在 Swift 里面可以通過函數 func 定義一個函數,也可以通過閉包表達式定義一個函數

    func sum(_ v1:Int,_ v2:Int) -> Int{
    
       return v1+v2
    }
    sum(1,2) // 3
    

    閉包的格式

    {    參數列表  -> 返回值類型 in
           具體的代碼
    }
    

    閉包的具體舉例

    var fn = {
    
        (_ v1:Int,_ v2:Int) -> Int in
    
        return v1+v2
    }
    
    fn(2,3)    // 5
    

    提示:上面僅僅是給閉包定義了一個變量,如果不寫變量和下面的意思一樣

    { 
       (_ v1:Int,_ v2:Int) -> Int in
    
        return v1+v2
    }(2,3)
    
  • 1.2、閉包表達式的簡寫
    基礎的閉包

    var fn = {
    
      (v1:Int,v2:Int) -> Int in
    
         return v1+v2
    }
    

    簡寫的代碼

    func exec(v1:Int,v2:Int,fn:(Int,Int)->Int){
      
       print(fn(v1,v2))
    }
    

    提示:傳進去一個閉包

    調用 exec 函數方式如下:等效,結果都是 20
    方式一

    exec(1, 2, fn: fn)
    

    方式二

    exec(v1: 10, v2: 10) { (v3, v4) -> Int in
         return v3 + v4
    }
    

    方式三

    exec(v1: 10, v2: 10, fn: {
           v3,v4 in return v3+v4
    })
    

    方式四

    exec(v1: 10, v2: 10, fn: {
           v3,v4 in v3+v4
    })
    

    方式五

    exec(v1: 10, v2: 10, fn: {$0 + $1})
    

    方式六

    exec(v1: 10, v2: 10, fn: +)
    
  • 1.3、尾隨閉包

    • 如果將一個很長的閉包表達式作為函數的最后一個實參,使用尾隨閉包可以增強函數的可讀性

    • 尾隨閉包是一個被書寫在函數調用括號外面(后面)的閉包表達式

      func exec(v1:Int,v2:Int,fn:(Int,Int)->Int){
      
           print(fn(v1,v2))
      }
      
      exec(v1: 6, v2: 7, fn: {$0 + $1})
      

      結果:13,讓兩個參數相加,我們還可以:-、* 等等

    • 如果閉包表達式是函數的唯一實參,而且使用了尾隨閉包的寫法,那就不需要再函數名后面寫圓括號

      func exec(fn:(Int,Int)->Int){
      
             print(fn(2,5))
      }
      

      調用方式如下,結果為 7

      exec(fn: {$0 + $1})   
      exec(){$0 + $1}
      exec{$0 + $1}
      
  • 1.4、數組的排序

    func cmp(i1:Int,i2:Int) -> Bool {
         // 大的排到前面
         return i1 > i2
    }
    // 返回 false: i1 排在 i2 后面,也就是 i1 的值小于 i2
    // 返回 true: i1 排在 i2 前面,也就是 i1 的值大于 i2
    
  • 1.5、忽略參數

    func exec(fn:(Int,Int)->Int){
         print(fn(1,5))
    }
    exec{_,_ in 11}  // 11
    
  • 1.6、閉包(Closure):的實質是一段有具體功能的代碼塊。

    • 閉包:一個函數和他捕獲的變量/常量環境組合起來,稱為閉包。閉包的核心是在其使用的局部變量/常量 會被額外的復制或者引用,使這些變量脫離其作用域后依然有效。
      • 一般指定義在函數內部的函數

      • 一般它捕獲的是外層函數的局部變量/常量

        // 返回的 plus 與 num 形成了閉包
        func getFn() -> Fn{
        
             var num = 0
             func plus(_ i:Int) -> Int{
        
                 num += I
                 return num
             }
        
             return plus
        }
        
        var fn1 = getFn()
        var fn2 = getFn()
        
        fn1(1)
        fn2(2)
        
    • 可以把閉包想象成一個類的實例對象
      • 存儲在堆空間

      • 捕獲的局部變量/常量就是對象的成員(存儲屬性)

      • 組成閉包的函數就是類內部定義的方法

        class Closure{
        
            var num = 2
            func plus(_ i:Int) -> Int {
                 num += I
                 return num
            } 
        }
        
        var cs1 = Closure()
        var cs2 = Closure()
        
        cs1.plus(2)   // 4
        cs2.plus(2)   // 4
        
        cs1.plus(3)   // 7
        cs2.plus(3)   // 7
        
  • 1.7、自動閉包
    自動閉包的條件:自動閉包參數使用有嚴格的條件是首先此閉包不能有參數,其次在調用函數傳參的時,此閉包的實現只能由一句表達式組成,閉包的返回值即為此表達式的值,自動閉包由 @autoclosure 來聲明,如下 例三

    • 例一:如果第1個數大于0,返回第一個數。否則返回第2個數

      func getFirstPositive(_ v1: Int, _ v2: Int) -> Int
      {
         return v1 > 0 ? v1 : v2 
      }
      getFirstPositive(10, 20)
      
    • 例二:改成函數類型的參數,可以讓v2延遲加載

      func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int?
      {
           return v1 > 0 ? v1 : v2()
      }
      getFirstPositive(-4) { 20 }
      
    • 例三:為了避免與期望沖突,使用了@autoclosure的地方最好明確注釋清楚:這個值會被推遲執行

      func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
           return v1 > 0 ? v1 : v2()
      }
      
      getFirstPositive(-4, 20)
      
    • 結論

      • @autoclosure 會自動將 20 封裝成閉包 { 20 }
      • @autoclosure 只支持 () -> T 格式的參數 @autoclosure并非只支持最后1個參數
      • 空合并運算符 ?? 使用了@autoclosure 技術
      • @autoclosure、無@autoclosure,構成了 函數重載
  • 1.8、逃逸閉包和非逃逸閉包

    • 逃逸閉包:是指函數內的閉包在函數執行結束后在函數外依然可以使用
    • 非逃逸閉包:是指在函數的聲明周期結束后,閉包也將會被銷毀。換句話說,非逃逸閉包只能在函數內部使用,在函數外部不能使用。默認情況下函數中的閉包都為非逃逸閉包,這樣做的優點是可以提高代碼的性能,節省內存消耗,開發者可以根據實際需求將閉包參數聲明為逃逸閉包。

      提示:

      • 非逃逸閉包也不可以作為返回值返回,如果這么做,編譯器會拋出一個錯誤。

      • 將閉包聲明為非逃逸型,需要使用 @noescape 修飾。需要注意的是,在最新的Xcode版本里面已經不需要再使用,參數默認都是非逃逸的,如下代碼

        只有一個閉包的函數,將此閉包聲明為非逃逸的,此閉包既不可最為返回值也不可賦值給外部變量
        在xcode10.1中會有警告,這個關鍵字可以忽略
        
        @noescape 默認的,不需要單獨再寫
      • 逃逸類型的閉包通常用于異步操作中,例如一個請求完成后要執行閉包回調,需要使用逃逸類型。

二、屬性

  • 2.1、Swift 里面跟實例相關的屬性可以分為2大類

    • 存儲屬性(Sored Property)
      • 類似于成員變量這個概念
      • 存儲在實例的內存中
      • 結構體和類可以定義存儲屬性
      • 枚舉不可以定義存儲屬性

      提示:

      • 關于存儲屬性,在Swift里面有個明確的規定,在創建類和結構體的時候,必須為所有的存儲屬性設置一個合適的初始值。

        struct Circle {
             /// 存儲屬性
             var radius:Double
        }
        var circle = Circle(radius: 2)
        
      • 可以在初始化器里面為存儲屬性設置一個初始值

        struct Circle {
             /// 存儲屬性
             var radius:Double
             init () {
                 radius = 2
             }
        }
        var circle = Circle()
        
      • 可以分配一個默認的屬性值作為屬性定義的一部分。如在定義屬性的時候直接給一個 值,var radius:Int = 2

        struct Circle {
             /// 存儲屬性
             var radius:Double = 2.0
        }
        var circle = Circle()
        
    • 計算屬性(Computed Property)
      • 本質就是方法(函數)
      • 不占用實例的內存
      • 枚舉、結構體、類 都可以定義計算屬性

      提示:

      • set傳入的新值默認叫做 newValue,也可以自定義
      • 定義計算屬性只能用 var ,不能用 let(代表常量,值是一成不變的)
      • 計算屬性的值是可能發生變化的(即使是只讀計算屬性)
      • set方法的話必須有get方法,有get方法可以沒有set方法
  • 2.2、以結構體舉例

    struct Circle {
           /// 存儲屬性
           var radius:Double
           /// 計算屬性
           var diameter:Double {
                set {
                    radius = newValue / 2.0
                }
                get {
                    radius * 2.0
                }
            }
    }
    
    var circle = Circle(radius: 2)
    circle.diameter = 20;
    //結果是 8 個字節
    print("占用的內存大小=\(MemoryLayout.stride(ofValue: circle))") 
    

    提示:計算屬性是不占內存的,原因是它的set 和 get 類似于兩個函數,如下代碼,由此可以看出是不占用內存的

    func setDiameter (newValue:Double) {
         radius = newValue / 2.0
    }
    func getDiameter (newValue:Double) -> Double {
         radius * 2.0
    }
    
  • 2.3、枚舉原始值 rawvalue 原理

    enum TestEnum : Int {
          case test1 = 1, test2 = 2, test3 = 3
          var rawValue: Int {
              switch self {
              case .test1:
                   return 8
              case .test2:
                   return 8
              case .test3:
                   return 10 
              }
          } 
    }
    print(TestEnum.test3.rawValue) // 結果 10
    

    枚舉原始值 rawValue 的本質是:只讀計算屬性,查看匯編里面只有 get 方法

  • 2.4、延遲存儲屬性(Lazy Stored Property)

    • 使用 lazy 可以定義一個延遲存儲屬性,在第一次用到屬性的時候才會進行初始化

      class Car {
          init() {
              print("Car init")
          }
          func run() {
              print("Car is running!")
          }
      }
      
      class Person {
          lazy var car = Car()
          init() {
              print("Person init")
          }
      
          func goOut() {
             car.run()
          }
      }
      // 使用
      let person = Person()
      print("------")
      person.goOut()
      

      打印結果如下:我們可以看到在 let person = Person()執行的時候 lazy var car = Car()沒有立馬執行,而是在用到 car 的時候才被調用的,由此可以看出在存儲屬性前面加上 lazy 是不會立馬執行的,在用到的時候才會被執行

      Person init
      ------
      Car init
      Car is running!
      

      提示

      • lazy 屬性必須是 var,不能是let
      • let 必須在實例的初始化方法完成之前就擁有值
      • 如果 多條線程 同時第一次訪問 lazy 屬性,是無法保證屬性只被初始化1次,這個是線程不安全的
      lazy var image:UIImage = {
         //圖片url
         let imgUrl = "http://www.uw3c.com/images/index/logo_monkey.png"/
         let url : NSURL = NSURL(string:imgUrl)!// 轉換網絡URL
         let data : NSData = NSData(contentsOf:url as URL)!
         return UIImage(data: data as Data)!
      }()
      
  • 2.5、延遲屬性的注意點

    • 當一個結構體包含一個延遲屬性的時候,只有 var 才能訪問延遲屬性,因為延遲屬性初始化時需要改變結構體的內存

      解釋:如果實例定義為 let,那么節結構體的內存就固定了,不能被修改,那么延遲屬性的本質是修改結構體的內存,編譯器就會直接報錯

      struct Point1 {
         var x = 1
         var y = 2
         lazy var z = 3
      }
      // 使用
      let point = Point1()
      point.z
      
  • 2.6、屬性觀察器

    struct Circle {
       /// 存儲屬性
       var radius:Double {
           willSet {
               print("willSet",newValue)
           }
      
           didSet {
               print("didSet",oldValue,radius)
           }
        }
        init() {
            self.radius = 1.0
            print("Circle init")
        }    
    }
    
    var circle = Circle()
    circle.radius = 10 
    print(circle.radius)
    打印結果如下
    Circle init
    willSet 10.0
    didSet 1.0 10.0
    10.0
    

    提示:

    • willSet 會傳遞新值,默認值叫 newValue
    • didSet 會傳遞舊值,默認值叫 oldValue
    • 在初始化器中設置屬性值不會觸發 willSetdidSet
    • 在屬性定義時設置初始值也不會觸發 willSetdidSet
  • 2.7、全局變量 和 局部變量

    • 屬性觀察器、計算屬性的功能,同樣可以應用在全局變量,局部變量身上

    • 全局變量

      var num:Int {
           get {
               return 10
           }
           set {
               print("setNum",newValue)
           }
      }
      num = 12  // setNum 12
      print(num) // 10
      
    • 局部變量

      fun test() {
           var age:Int = 10 {
              willSet {
                  print("willSet",newValue)
              }
              didSet {
                  print("didSet",oldValue,radius)
              }
           }
           age = 11
           // 打印:willSet 11
           // didSet 10 11
      }
      // 調用方法
      test()
      
  • 2.8、inout 本質的探究

    struct Shape {
          var width:Int
          var side:Int {
              willSet {
                 print("willSet - side",newValue)
              }
              didSet {
                 print("willSet - side",oldValue,side)
              }
           }
           var girth:Int {
               set {
                  width = newValue / side
                  print("setGirth",newValue)
               }
               get {
                  print("getGirth")
                  return width * side
               }
            }
    
            func show() -> Void {
                print("width = \(width), side = \(side), girth = \(girth)")
            }
    }
    
    func test(_ num: inout Int) -> Void {
        num = 20
    }
    
    var s = Shape(width: 10, side: 4)
    test(&s.width)
    s.show()
    print("----------")
    test(&s.side)
    s.show()
    print("----------")
    test(&s.girth)
    s.show()
    打印結果是
    
    getGirth
    width = 20,side = 4,width = 80
    ----------
    willSet - side 20
    willSet - side 4 20
    getGirth
    width = 20,side = 20,width = 400
    ----------
    getGirth
    setGirth 20
    getGirth
    width = 1,side = 20,width = 20
    
    • 如果實參有物理內存地址,且沒有設置屬性觀察器:直接將實參的內存地址傳入函數(實參進行引用傳遞)
    • 如果實參是計算屬性 或者 設置了屬性觀察器:采取了 Copy In Copy Out 的做法
      • 調用函數時,先復制實參的值,產生副本 【get】
      • 將副本的內存地址傳入函數 (副本進行引用傳遞),在函數的內部可以修改副本的值
      • 函數返回后,再將副本的值覆蓋實參的值 【set】
    • 總結:inout 的本質是引用傳遞 (地址傳遞)
  • 2.9、類型屬性(Type Property)
    嚴格來說屬性可以分為:實例屬性 和 類型屬性

    • 實例屬性(Instance Property):只能通過實例去訪問

      • 存儲實例屬性(Stored Instance Property):存儲在實例內存中,每個實例都有一份內存
      • 計算實例屬性(Computed Instance Property)
    • 類型屬性(Type Property):只能通過類去訪問

      • 存儲類屬性(Stored Instance Property):整個程序運行中,就只有一份內存(類似于全局變量)

      • 計算類屬性(Computed Instance Property)

      • 可以通過 static 定義類型屬性,如果是類可以使用關鍵字 class

        struct Car {
             static var count = 0
             init() {
                Car.count += 1
             }
        }
        let c1 = car()
        let c2 = car()
        let c3 = car()
        print(Car.count) // 結果是 3
        
    • 總結:類屬性細節

      • 不同于 存儲實例屬性,你必須給 存儲類型屬性 設定初始值,因為類型沒有像實例那樣的init初始化器來初始化存儲屬性
      • 存儲類型屬性默認就是lazy,會在第一次使用的時候才初始化,就算被多個線程同時訪問,保證只會初始化一次
      • 存儲類型屬性可以是 let

      提示:枚舉類型也可以定義類型屬性(存儲類型屬性、計算類型屬性)

    • 類存儲屬性的使用-- 單利

      class Person {
         public static let share = Person() // 類存儲屬性,在這個程序中只有一份內存
         private init() {}
      }
      

三、方法

  • 3.1、類、枚舉、結構體 都可以定義 實例方法類型方法

    • 實例方法(Instance Method):通過實例對象調用

    • 類型方法(Type Method):通過 類型調用,用 class/static關鍵字定義

      struct Car {
          static var count = 0
          init() {
              Car.count += 1
          }
          // 在函數(方法) 前面加上 class/static關鍵字 就可以變成類方法,用類名來調用
          static func getCount() -> Int { count }
      }
      let c1 = car()
      let c2 = car()
      let c3 = car()
      print(Car.getCount()) // 結果是 3
      

      提示:self

      • 在實例方法里面代表實例對象
      • 在類方法里面代表類型
        上代碼代碼 static func getCount() 里面的 count 等價于 self.count、Car.self.countCar.count
  • 3.2、mutating 的使用

    • 結構體和枚舉都是值類型,默認情況下,值類型的屬性不能被自身的實例方法修改

    • 解決辦法:在 func 前面加上 mutaing 可以允許這種修改行為

      struct Point {
         var x = 0.0,y =  0.0
         mutating func moveBy(deltaX:Double,deltaY:Double)  {
             x += deltaX
             y += deltaY
             // 等效于上面的代碼
             //self = Point2(x: x + deltaX, y: y + deltaY)
         }
       }
      
      enum StateSwitch {
         case low,middle,high
         mutating func next() {
             switch self {
             case .low:
                  self = .middle
             case .middle:
                  self = .high
             case .high:
                  self = .low
             }
         }
      }
      
  • 3.3、@discardableResult 消除方法返回值沒使用的警告,如下

    struct Point {
         var x = 0.0,y =  0.0
         @discardableResult mutating func moveBy(deltaX:Double,deltaY:Double) -> Double  {
               x += deltaX
               y += deltaY
               return x + y
          }   
    }
    var point = Point()
    point.moveBy(deltaX: 2.0, deltaY: 2.0)
    

    提示:如果 函數moveBy不加 @discardableResult ,point.moveBy(deltaX: 2.0, deltaY: 2.0) 會提提示 Result of call to 'moveBy(deltaX:deltaY:)' is unused

四、下標(subscrip)

  • 4.1、使用 subscrip 可以給任意類型(枚舉、結構體、類)增加下標功能,有些地方翻譯為:下標腳本

  • 4.2、subscrip的語法類似于實例方法、計算屬性,本質就是方法 (函數)

    struct Point {
        var x = 0.0,y = 0.0
        subscript(index: Int) -> Double {
      
             set {
                if index == 0 {
                    x = newValue
                } else if index == 1 {
                    y =  newValue
                }
             }
             get {
                if index ==  0 {
                   return x
                } else if index == 1 {
                   return y
                }
                return 0
             }
        }
    }
    
    var p = Point5()
    p[0] = 1.1
    p[1] = 2.2
    print(p.x)  // 1.1
    print(p.y)  //  2.2
    print(p[0]) // 1.1
    print(p[1]) // 2.2
    

    提示:

    • subscript 中定義的返回值類型決定了:get 方法的返回值 和 set 方法中 newValue 的類型
    • subscript 可以接受多個參數,并且類型任意
  • 4.3、subscript 下標細節

    • 細節一:subscript 下標可以沒有 set 方法,但必須有 get 方法,如果只有get方法可以把 get 關鍵字去掉,如下

      struct Point {
         var x = 0.0,y = 0.0
         subscript(index: Int) -> Double {
                if index ==  0 {
                    return x
                } else if index == 1 {
                    return y
                }
                return 0
         }
      }
      
    • 細節二:可以設置參數標簽

      struct Point {
          var x = 0.0,y = 0.0
          subscript(index i: Int) -> Double {
              if i == 0 {
                  return x
              } else if i == 1 {
                  return y
              }
              return 0
           }
      }
      var p = Point6()
      p.y = 22.0
      // 訪問的時候必須加上參數標簽
      print(p[index: 1])
      
    • 細節三:下標可以是 類型方法

      class Sum {
          static subscript(v1:Int,v2:Int) -> Int {
                return v1 + v2
          }
      }
      print(Sum[10,20])  // 打印結果是 30
      
  • 4.4、接收多個參數的下標

    class Grid {
          var data = [
              [0, 1, 2],
              [3, 4, 5],
              [6, 7, 8]
           ]
          subscript(row: Int, column: Int) -> Int {
                set {
                    guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                       return
                    }
                    data[row][column] = newValue
                }
                get {
                    guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                       return 0 
                    }
                    return data[row][column]
                }
          } 
    }
    var grid = Grid()
    grid[0, 1] = 77  // 第1行第2列
    grid[1, 2] = 88  // 第2行第3列
    grid[2, 0] = 99  // 第3行第1列
    print(grid.data)
    

    打印結果如下:

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

推薦閱讀更多精彩內容