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阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容