Swift: 基础部分

Swift中文文档

一、常量和变量

  • 常量: 不可变的量, 使用 let 修饰
let maximumNumberOfLoginAttempts = 10
  • 变量: 可以修改值的量, 使用 var 修饰
var currentLoginAttempt = 0
  • 可以在一行中声明多个常量或变量, 用逗号隔开
var x = 0, y = 0, z = 0

注意: 如果在代码中, 有不需要修改的值, 请使用let关键字声明为常量, 只将需要修改的值声明为变量

二、类型标注

  • 在声明常量和变量时, 可以在常量或变量名的后面加上 : 类型名, 给常量或变量加上类型标注
var welcomeMessage: String
  • : 类型名: 代表着是...类型, 所以这行代码可以理解为: 声明一个类型为String, 名字为welcomMessage的变量

  • 类型为String的意思是可以存储任意String类型的值

  • 可以在一行中定义多个同样类型的变量, 用逗号分割, 并在最后一个变量名之后添加类型标注

var red, green, blue: Double

三、常量和变量的命名

  • 常量和变量可以包含任何字符, 包括Unicode字符:
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
  • 常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。

  • 当声明多个常量或变量时, 这些常量名和变量名不能出现重复, 不能改变其存储值的类型, 同时, 也不能将常量和变量互转

注意: Swift中保留了一些关键字, 所以在命名时如果与关键字重名, 需要使用反引号(`)将关键字包围的方式将其作为名字使用。请尽量避免使用关键字做为常量或变量名, 除非你别无选择

四、输出常量和变量

  • print(_:separator:terminator:): 是Swift中的输出函数
1、参数解释
  • 第一个参数_: 是我们需要输出的项, 如果是多个输出项, 可以使用逗号,隔开
print(1, 2, 3, "4", "5", "6")

控制台打印: 
1 2 3 4 5 6
  • 第二个参数separator: 表示多个输出项之间, 在输出时用什么分割, 默认是空格, 所以上述代码在打印时, 每个项之间会有个空格分割
print(1, 2, 3, "4", "5", "6", separator: "-")

控制台打印: 
1-2-3-4-5-6
  • 第三个参数terminator: 当print的结尾需要打印的字符, 默认是换行符\n
// 使用默认terminator参数:
print(1, 2, 3, "4", "5", "6")
print(1, 2, 3, "4", "5", "6", separator: "-")

控制台打印: 
1 2 3 4 5 6
1-2-3-4-5-6

// 指定terminator参数为空格:
print(1, 2, 3, "4", "5", "6", terminator: " ")
print(1, 2, 3, "4", "5", "6", separator: "-")

控制台打印: 
1 2 3 4 5 6 1-2-3-4-5-6
2、字符串插值
  • Swift中可以使用字符串插值的方式把常量名或变量名当做占位符加入到长字符串中, Swift会用当前常量或变量的值替换这些占位符
  • 将常量或变量名放入圆括号中, 并在开括号前使用反斜杠将其转义:
let abc = "hellow"
print("\(abc) world!")

控制台打印: 
hellow world!

五、注释

  • 单行注释
// 这是单行注释
  • 多行注释
/* 这也是一个注释
但是是多行的 */
  • 嵌套注释: 多行注释可以嵌套另一个多行注释
/* 这是一个多行注释的开头
/* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */

使用多行注释, 你可以快速方便的注释掉一大段代码, 即使这段代码之中已经含有多行注释

六、分号

  • Swift并不强制在每条语句的结尾处使用分号(;), 当然添加上也不算错
  • 但是有一种情况必须使用分号, 就是将多条语句写在同一行内
let cat = "🐱"; print(cat)
// 输出 "🐱"

七、整数

1、有符号和无符号类型
  • 整数就是没有小数部分的数字
  • Swift提供了8、16、32和64位的有符号和无符号整数类型
let a: Int8 = 10
let b: Int16 = 10
let c: Int32 = 10
let d: Int64 = 10

let e: UInt8 = 10
let f: UInt16 = 10
let g: UInt32 = 10
let h: UInt64 = 10
2、整数范围
  • 可以访问不同整数类型的minmax属性来获取对应类型的最大值和最小值
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
  • minmax 所传回值的类型, 正是其所对的整数类型(如上例 UInt8, 所传回的类型是 UInt8), 可用在表达式中相同类型值旁。
3、Int
  • Swift提供了一个特殊的有符号类型Int, 长度与当前平台的原生字长相同
  • 在32位平台上, IntInt32长度相同
  • 在64位平台上, IntInt64长度相同
4、UInt
  • Swift 也提供了一个特殊的无符号类型 UInt,长度与当前平台的原生字长相同:
  • 在32位平台上,UIntUInt32 长度相同。
  • 在64位平台上,UIntUInt64 长度相同。

八、浮点数

  • 浮点数是有小数部分的数字, 比如 3.141590.1-273.15
  • 浮点类型比整数类型表示的范围更大, 可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
  • Double 表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
  • Float 表示32位浮点数。精度要求不高的话可以使用此类型。

九、类型安全和类型推断

1、类型安全
  • Swift是一个类型安全的语言。类型安全的语言可以让开发者更清楚地知道代码要处理的值的类型

  • 由于Swift是类型安全的, 所以会在编译代码时进行类型检查, 并把不匹配的类型标记为错误

  • 如果你的代码需要一个 String, 你绝对不可能不小心传进去一个 Int。如果传入, 编译器就会报错

let message: String = 10
// 报错: Cannot convert value of type 'Int' to specified type 'String'
2、类型推断
  • 在声明常量和变量时, 并不需要显示的指定类型, 编译器会根据等号(=)右面的值来推断出当前定义的常量和变量的类型
let meaningOfLife = 42
// meaningOfLife 会被推测为 Int 类型, 不需要写成 let meaningOfLife: Int = 42
  • 当推断浮点数的类型时, Swift 总是会选择 Double 而不是 Float。
let pi = 3.14159
// pi 会被推测为 Double 类型
  • 如果表达式中同时出现了整数和浮点数, 会被推断为 Double 类型:
let anotherPi = 3 + 0.14159
// anotherPi 会被推测为 Double 类型

十、数值型字面量

  • 整数字面量可以被写作:
一个十进制数,没有前缀
一个二进制数,前缀是 0b
一个八进制数,前缀是 0o
一个十六进制数,前缀是 0x
  • 下面的所有整数字面量的十进制值都是 17:
let decimalInteger = 17
let binaryInteger = 0b10001       // 二进制的17
let octalInteger = 0o21           // 八进制的17
let hexadecimalInteger = 0x11     // 十六进制的17
  • 如果一个十进制数的指数为 exp,那这个数相当于基数和10^exp 的乘积:
1.25e2 表示 1.25 × 10^2,等于 125.0。
1.25e-2 表示 1.25 × 10^-2,等于 0.0125。
  • 如果一个十六进制数的指数为 exp,那这个数相当于基数和2^exp 的乘积:
0xFp2 表示 15 × 2^2,等于 60.0。
0xFp-2 表示 15 × 2^-2,等于 3.75。

十一、整数转换

  • 不同整数类型的变量和常量可以存储不同范围的数字。
  • Int8 类型的常量或者变量可以存储的数字范围是 -128~127, 而 UInt8 类型的常量或者变量能存储的数字范围是 0~255
  • 如果数字超出了常量或者变量可存储的范围, 编译的时候会报错:
let cannotBeNegative: UInt8 = -1
// UInt8 类型不能存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1
// Int8 类型不能存储超过最大值的数,所以会报错
  • 当两种整数类型的数字进行运算时, 需要将这两种整数类型, 转换成同一种类型之后, 再运算
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
  • 现在两个数字的类型都是 UInt16,可以进行相加。目标常量 twoThousandAndOne 的类型被推断为 UInt16,因为它是两个 UInt16 值的和。

  • SomeType(ofInitialValue) 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,UInt16 有一个构造器,可以接受一个 UInt8 类型的值,所以这个构造器可以用现有的 UInt8 来创建一个新的 UInt16

注意,你并不能传入任意类型的值,只能传入 UInt16 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型)

十二、整数和浮点数转换

  • 整数和浮点数的转换必须显式指定类型:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推测为 Double 类型
  • 这个例子中, 常量 three 的值被用来创建一个 Double 类型的值, 所以加号两边的数类型须相同。如果不进行转换, 两者无法相加。

  • 浮点数到整数的反向转换同样行, 整数类型可以用 Double 或者 Float 类型来初始化:

let integerPi = Int(pi)
// integerPi 等于 3, 所以被推测为 Int 类型
  • 当用这种方式来初始化一个新的整数值时, 浮点值会被截断。也就是说 4.75 会变成 4, -3.9 会变成 -3。

注意
结合数字类常量和变量不同于结合数字类字面量。字面量 3 可以直接和字面量 0.14159 相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。

十三、类型别名

  • Swift中, 可以给现有的类型起一个新的名字, 使用关键字typealias, 新名与原始名用法一致
typealias AudioSample = Int8
  • 定义了一个类型别名之后, 你可以在任何使用原始名的地方使用别名:
let min = AudioSample.min
let max = AudioSample.max
print(min, max)
// 控制台打印: -128 127

十四、布尔值

  • Swift中有一个基本的布尔类型, 叫做Bool, 布尔值指逻辑上的值, 因为他只能是 真或者假
  • Swift有两个布尔常量, truefalse
let orangesAreOrange = true
let turnipsAreDelicious = false
// orangesAreOrange 和 turnipsAreDelicious 的类型会被推断为 Bool, 因为它们的初值是布尔字面量
  • 当你编写条件语句比如 if 语句的时候,布尔值非常有用:
if turnipsAreDelicious {
    print("Mmm, tasty turnips!")
} else {
    print("Eww, turnips are horrible.")
}
// 输出 "Eww, turnips are horrible."
  • 如果在需要使用Bool类型的地方使用了非布尔值, Swift的类型安全机制会报错, 下面的例子会报告一个编译时错误:
let i = 1
if i {
    // 这个例子不会通过编译,会报错
}
  • 然而, 下面的例子是合法的:
let i = 1
if i == 1 {
    // 这个例子会编译成功
}
// i == 1 的比较结果是 Bool 类型,所以可以通过类型检查

十五、元组

  • 元组: 把多个值组合成一个复合值, 元组内的值可以是任意类型, 并不要求是相同类型
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")
  • 可以将元组的内容分解成单独的常量或变量
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 输出 "The status code is 404"
print("The status message is \(statusMessage)")
// 输出 "The status message is Not Found"
  • 如果你只需要一部分元组值, 分解的时候可以把要忽略的部分用下划线(_)标记:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 输出 "The status code is 404"
  • 此外还可以通过下标访问元组中的单个元素, 下标从零开始:
print("The status code is \(http404Error.0)")
// 输出 "The status code is 404"
print("The status message is \(http404Error.1)")
// 输出 "The status message is Not Found"
  • 可以在定义元组的时候给单个元素命名:
let http200Status = (statusCode: 200, description: "OK")
  • 给元组中的元素命名后, 可以通过名字来获取这些元素的值:
print("The status code is \(http200Status.statusCode)")
// 输出 "The status code is 200"
print("The status message is \(http200Status.description)")
// 输出 "The status message is OK"
  • 元组做为函数的返回值时, 会非常有用, 可以返回多个值

注意
元组在临时组织值的时候很有用, 但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用, 请使用类或者结构体而不是元组

十六、可选类型

1、可选类型
  • 在OC中, 给对象赋值, 或者方法返回一个对象时, 可以使用nil, 来表示缺少一个合法的对象
NSString *name = nil;
  • 在Swift中, 一个非可选类型的值, 是不能赋值为nil
var abc: Int = nil
// 这是错误的, abc的类型是 Int, 并不是可选类型 Int?
  • 可选类型的类型名, 就是在类型名的后面加一个问号, 例如: Int?, String?, Double?
  • 一个可选的Int被写作Int?, 问号表示包含的值是可选类型, 也就是说可能包含Int值, 也可能不包含值
2、nil
  • 可以给变量赋值为nil, 来表示它没有值:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值

注意
Swift的nil和OC中的nil并不一样, 在OC中, nil是一个指向不存在对象的指针。在Swift中, nil不是指针, 它是一个确定的值, 用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。

3、if 语句判断
  • 可以使用if语句和nil比较来判断一个可选值是否包含值
  • 如果可选类型有值, 他将不等于nil
if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// 输出 "convertedNumber contains some integer value."
4、强制解析
  • 一个可选类型的变量, 打印如下
var abc: Int? = nil
print(abc)        // 打印: nil
abc = 123
print(abc)        // 打印: Optional(123)
  • 可选类型的变量不能直接使用, 因为他的类型是Optional, 表示有值或者空值, 如果想要使用可选类型的变量, 就必须对该变量进行解包

  • 当确定可选类型确实包含值之后(非nil), 可以在可选的名字后面加一个感叹号(!)来获取值, 这个感叹号表示我知道这个可选有值, 非nil, 请使用它, 这被称为可选值的强制解析

注意:
使用!来获取一个不存在的可选值会导致运行错误, 使用(!)来强制解析之前, 一定要确定可选包含一个非nil的值

5、可选绑定(if let)
  • 可选绑定: 可以判断一个可选类型是否有值, 如果有值就会把这个值赋给一个临时常量或者变量

  • 可选绑定可以用在 ifwhile 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。

  • 未使用可选绑定

let abc: Int? = 123
print(abc)            // 打印: Optional(123)
  • 使用if let可选绑定
var abc: Int? = 123
if let def = abc {
  print(def)         // 打印: 123
}
  • 使用while let可选绑定
let abc: Int? = 123
while let abc = abc {
    print(abc)         // 打印: 123
    break;
}
  • 可以将lf let改为lf var, 这样def就是一个变量而非常量

  • 可以使用多个可选绑定或多个布尔条件包含在一个if语句中

if let firstNumber = Int("4"),
 let secondNumber = Int("42"),
 firstNumber < secondNumber 
 && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// 输出 "4 < 42 < 100"

if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// 输出 "4 < 42 < 100"

注意:
if条件语句中使用常量或变量来创建一个可选绑定, 仅在if语句的句中body中才能获取到值, while语句亦然

6、可选绑定
  • 有时候在程序架构中, 第一次被赋值之后, 可以确定一个可选类型在之后会一直有值, 在这种情况下, 每次都要判断和解析可选值是非常低效的, 因为可以确定它总会有值

  • 这种情况下, 可以将可选类型定义为隐式解析可选类型, 把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型

  • 一个隐式解析可选类型其实就是一个普通的可选类型, 直接打印如下:

var abc: String! = nil
print(abc)          // 打印: none
abc = "123"
print(abc)          // 打印: some("123")
  • 但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString  // 不需要感叹号

注意
如果在隐式解析可选类型没有值的时候尝试取值, 会触发运行时错误, 和在没有值的普通可选类型后面加一个感叹号一样

  • 仍然可以把隐式解析可选类型当做普通可选类型来判断是否包含值:
if assumedString != nil {
    print(assumedString)
}
// 输出 "An implicitly unwrapped optional string."
  • 也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值
if let definiteString = assumedString {
    print(definiteString)
}
// 输出 "An implicitly unwrapped optional string."

注意
如果一个变量之后可能变成nil的话请不要使用隐式解析可选类型, 如果你需要在变量的生命周期中判断是否是nil的话, 请使用普通可选类型

十七、错误处理

  • 当一个函数遇到错误条件, 它能够报错
func canThrowAnError() throws {
    // 这个函数有可能抛出错误
}
  • 一个函数可以通过声明中添加throws关键词来抛出错误消息, 当你的函数能跑出错误消息时, 你应该在表达式中前置try关键词, 并使用do catch接受错误
do {
    try canThrowAnError()
    // 没有错误消息抛出
} catch {
    // 有一个错误消息抛出
}
  • 一个 do 语句创建了一个新的包含作用域,使得错误能被传播到一个或多个 catch 从句。

十八、断言和先决条件

  • 断言和先决条件是在运行时所做的检查, 可以用他们来检查在执行后续代码之前, 某一个必要的条件是否已经被满足了

  • 如果断言或者先决条件中的布尔条件评估为true, 则代码像往常一样继续执行, 如果评估为false, 程序的当前状态无效, 代码执行结束, 程序终止

  • 断言帮助我们在开发阶段找到错误和不正确的假设, 先决条件帮助我们在生产环境中检测到存在的问题

  • 断言和先决条件的不同点

断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。
在生产环境中,断言的条件将不会进行评估。
这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。
1、使用断言进行调试
  • 可以调用Swift标准库的assert(_:_:file:line:)函数写一个断言
  • 向这个函数传入一个结果为true或者false的表达式以及一条信息, 当表达式的结果为false的时候这条信息会被显示
let age = -3
assert(age > 0, "年龄必须大于0")
// 因为 age < 0,所以断言会触发
  • 如果不需要断言信息, 可以省略掉
assert(age >= 0)
  • 如果已经使用代码检查了条件, 例如使用if语句判断, 可以使用assertionFailure(_:file:line:)函数来表明错误信息
if age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
    print("You can ride the ferris wheel.")
} else {
    // 如果走入这里, 调试阶段会直接中断程序
    assertionFailure("A person's age can't be less than zero.")
}
2、强制执行先决条件
  • 当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件

  • 可以使用全局precondition(_:_:file:line:)函数来写一个先决条件, 向这个函数传入一个结果为true或者false的表达式以及一条信息, 当表达式的结果为false的时候这条信息会被显示:

// 在一个下标的实现里...
let index = -1
precondition(index > 0, "Index must be greater than zero.")

注意
如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。

  • 你能使用 fatalError(_:file:line:) 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError("Unimplemented")作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容