Kotlin学习笔记

项目未来可能需要使用kotlin开发,所以特此记录一下学习笔记,仅供参考,方便后期查询。已同步到GitHub上:KotlinTest

Kotlin 简介

kotlin 的目标是成为一门全栈语言,主要有以下的特点:

  • 已经成为Android的官方推荐语言
  • 百分百的和java兼容,两者可以相互转换
  • JSJVMNative多平台开发

数据类型

1. 基本类型

Boolean true/false
Double 64
Float  32
Long   64
Int    32
Short  32
Byte   8

val aChar = '0'
val bChar = '我'
val cChar = '\u000f'

Char类型的转义字符

\t          制表符
\b          光标后退一个字符
\n          回车
\r          光标回到行首
\'          单引号
\"          双引号
\\          反斜杠
\$          美元符号,Kotlin 支持美元符号开头的字符串模板

2. 基本类型的转换

不可隐式转换

val anInt: Int = 5
val aLong: Long = anInt.toLong()

必须得通过.to类型的方式进行数据的转换

字符串

  • 一串Char

  • 用双引号""引起来

    val aString: String = "Hello World!"

  • 字符串比较

    a == b 表示比较内容 类似 Java 中的 equals
    a === b 表示比较对象是否相同

  • 字符串模板

    println("hello, $name") -> "hello, 小明"

3. Koltin 中的类和对象初始化

类的定义

  • 类,一个抽象的概念
  • 具有某些特征的事物的概括
  • 不特定指代任何一个具体的事物

一般写法:

/**
* 其中类参数如果加上 var 修饰,那么他便是成员变量,反之则是普通的参数
*/
class Student(var name: String, var age: Int){
    init {
        // ... 相当于构造函数中的代码
    }
}

对象

  • 是一个具体的概念,与类相对

  • 描述某一个类的具体个体

  • 举例:

    某些人、领导的车等等

类和对象的关系

  • 一个类通常可以有很多歌具体的对象
  • 一个对象本质上只能从属一个类
  • 某一个人,他是工程师,但本质上还是属于人这一类

一般写法:

val student: Student = Student("xiaweizi", 23)

类的继承

  • 提取多个类的共性得到一个更为抽象的类,即父类
  • 子类拥有父类的一切特征
  • 子类也可以定义自己的特征
  • 所有的类最终继承自Any,类似于java中的Object

4. 空类型和智能转换

空类型

// 定义
val notNull: String = null // 错误,不可能为空
val nullanle: String? = null // 正确,可以为空
// 使用
notNull.length // 正确,不可能为空所以可以直接使用
nullable.length // 有可能为空,不能直接获取长度
// 要想获取长度,可以通过以下两者方式
nullable!!.length // 正确,强制认定 nullable 不可能为空,如果为空则会抛出空指针异常
nullable?.length // 正确,若 nullable 为空,则返回 null

智能类型转换

val child: Child = parent as Child // 类似于 Java 的类型转换,失败则抛出异常
val child: Child = parent as? Child // 如果转换失败,返回 null

编译器智能识别转换:

val parent: Parent = Child()
if (parent is Child) {
    // parent 直接调用子类方法,不需要再进行强制转换
}

val string: String = null
if (string != null) {
    // string.length 可以直接调用length 方法
}

5. 区间

一个数学上的概念,表示范围, ClosedRange的子类,IntRange最常用

基本用法:

0..100 --> [0, 100]
0 until 100 --> [0, 100)
i in 0..100 表示 i 是否在区间[0, 100]中

6. 数组

基本写法:

val ints: IntArray = IntArrayOf(1,2,3,5)
var charArray: CharArray = charArrayOf('a', 'b', 'c', 'd', 'e')
var stringArray: Array<String> = arrayOf("aa", "bb", "cc", "dd", "e")

基本操作:

print(charArray[index])
ints[0] = 2
ints.length
cahrArray.joinToString("") // 讲 char 数组转换成字符串
stringArray.slice(1..4) // 取出区间里的值

程序结构

1. 常亮和变量

常量

val a = 2
类似 Java 中的 final
不可被重复赋值
运行时常量:val x = getX()
编译期常量:const val x = 2

变量

var a = 2
a = 3 // 可以被再次赋值

类型推导

val string = "Hello" // 推导出 String 类型
val int = 5 // 推导出 Int 类型
var x = getString() + 5 // String 类型

2. 函数 Function

以特定功能组织起来的代码块

// 最简单的打印信息,无返回的方法
fun printMessage(message: String):Unit{
    println("$message")
}
// 拥有返回值得方法
fun sum(first: Int, second: Int):Int {
    return first + second
}
// 可以简化成:
fun sum(first: Int, second: Int) = first + second
// 或者更简单的匿名函数
val result = fun(first: Int, second: Int) = first + second

3. Lambda 表达式

其实又是匿名函数

一般形式

{传入参数 -> 函数体,最后一行是返回值}
// 例如
val sum = {first: Int, second: Int -> first + second}
val printMessage = {message: String -> println(message)}

类型标识

() -> Unit // 无参,返回值为 null
(Int) -> Int // 传入整型,返回一个整型
(String, (String) -> String) -> Boolean // 传入字符串、Lambda 表达式,返回Boolean

Lambda 表达式的简化

  • 函数参数调用时最后一个Lambda可以移出去
  • 函数参数只有一个Lambda,调用时小括号可以省略
  • Lambda只有一个参数可默认为it
  • 入参、返回值与形参一致的函数可以用函数引用方式作为实参传入

4. 成员变量和成员方法

成员变量的声明

// 第一种是在构造函数中声明
class Student(var age: Int, name: String){
    // age 是成员变量 name 是局部变量
}
// 第二种是在函数体内声明
var a = 0
    get() {
        field += 1
        return field
    }
    set(value) {
        println("set)
        field = value + 1
    }
// 可以进行对 get 和 set 方法的重新定义

// 属性的初始化尽量在构造方法中完成
// var 用 lateinit 延迟初始化, val 用 lazy

lateinit var sex: String
val person: Person by lazy {
    Person()
}

成员方法

在类中直接声明方法可以直接调用,包括lambda表达式

// 方法的声明
fun sum(a: Int, b: Int) = a + b
val sum1 = {a: Int, b: Int -> a + b}
// 方法的调用
println(person.sum(1,2))
println(person.sum1(3,5))

5. 运算符

java中运算符是不能重新定义重载的,只能按照原先的逻辑进行计算

Kotlin则可以重新定义运算符,使用operator关键字,举了例子:

// 定义一个用于计算复数的类
class Complex(var real: Double, var imaginary: Double) {
    operator fun plus(other: Complex): Complex{
        return Complex(real+other.real, imaginary+other.imaginary)
    }
    
    // 重新 toString 方法
    overrride fun toString(): String {
        return "$real + ${imaginary}i"
    }
}
// 使用
val complex1 = Complex(1, 2)
val complex2 = Complex(2, 3)
println(complex1 + complex2)
// 输出结果为
"3 + 5i"

关键就是这个方法,方法名必须是plus或者其他官方定义的运算符,参数有且仅有一个,类型自定义,返回值意识可以自定义的.

operator fun plus(other: Complex): Complex{
        return Complex(real+other.real, imaginary+other.imaginary)
}

6. 表达式

中缀表达式

通过infix关键字修复方法,那么就可以不用通过 对象.方法() 的方式调用,而是直接 对象 方法名 参数的方式调用。举了例子

class Student(var age: Int){
    infix fun big(student: Student): Boolean {
        return age > student.age
    }
}
// 如果没有 infix 的调用方式:
println(Student(23).big(Student)(12))
// 如果使用 infix 修饰的调用方式:
println(Student(23) big Student(12))

if表达式

直接来个例子

val a = 20
val b = 30
val flag: Int = if(a > b) a else b

When 表达式

加强版的 switch,支持任意类型, 支持纯粹表达式条件分支(类似if),举个栗子:

val a = 5
when(a) {
    is Int -> println("$a is Int")
    in 1..6 -> println("$a is in 1..6")
    !in 1..4 -> println("$a is not in 1..4")
    else -> {
        println("null")
    }
}

for循环

基本写法

for (element in elements)

while循环

基本写法

while() {
}
do {
} while()

跳过和终止循环

跳过当前循环用 continue
终止循环用 break

6. 异常捕获

同样也是表达式,可以用来赋值,举个例子

return try{
            x/y
        }
        catch(e: Exception) {
            0
        } finally {
            //...
        }

如果没有异常则返回x/y,否则返回0,finally中的代码无论如何还是要执行的。

7. 具名参数、变长参数和默认参数

具名参数:给函数的实参附上形参

fun sum(first: Int, second: Int) = first + second
sum(second = 2, first = 1)

变长参数:varary修饰,使用起来是和数组一样,某个参数可以接收多个值,可以不作为最后一个参数,如果传参时有歧义,需要使用具名参数。

fun hello(vararg ints: Int, string: String) = ints.forEach(println(it))
hello(1,3,4,5,string = "hello")
// 如果最后一个参数也是 Int
fun hello(varary ints: Int, anInt: Int)
// 创建数组
val arrayInt: IntArray = intArrayOf(1, 2, 3, 4)
hello(ints = *arrayInt, anInt = 2)

默认参数:就是给参数传入一个默认的值

fun hello(anInt: Int = 1, string: String)
hello(string = "aaa")

面向对象

1. 继承

继承语法要点:

  • 父类需要open才可以被继承
  • 父类方法、属性需要open才可以被覆写
  • 接口、接口方法、抽象类默认为open
  • 覆写父类(接口)成员需要override关键字

语法要点:

  • class A: B(), C, D
  • 继承类时实际上调用了父类的构造方法
  • 类只能单继承,接口可以多实现

接口代理:

一个类可以直接将自己的任务委托给接口的方法实现,举个例子:

interface Drive{
    fun drive()
}

interface Sing{
    fun sing()
}

class CarDrive: Drive{
    override fun drive() {
        println("我会开车呦")
    }
}

class LoveSing: Sing{
    override fun sing() {
        println("我会唱歌呦")
    }
}

class Manager(drive: Drive, sing: Sing): Drive by drive, Sing by sing

fun main(args: Array<String>) {
    val carDrive = CarDrive()
    val loveSing = LoveSing()
    val manager = Manager(carDrive, loveSing)
    manager.drive()
    manager.sing()
}

这样,manager不用做任何事情,完全交付给接口实现.

接口方法冲突:

接口方法可以有默认实现,通过super<父类名>.方法名

interface A{
    fun a() = 0
}

interface B{
    fun a() = 1
}

interface C{
    fun a() = 2
}

class D(var aInt: Int): A,B,C{
    override fun a(): Int {
        return when(aInt){
            in 1..10 ->{
                super<A>.a()
            }
            in 11..100 ->{
                 super<B>.a()
             }
            else -> {
                println("dd")
                super<C>.a()
            }
        }
    }
}

2. 类及成员的可见性

java类似,private、protected、public,其中internal代表的是模块内可见

3. Object

相当于Java中的单例模式,有以下特点

  • 只有一个实例的类

  • 不能自定义构造方法

  • 可以实现接口、继承父类

  • 本质上就是单例模式最基本的实现

      interface getDataSuccess{
          fun success()
      }
      
      abstract class getDataField{
          abstract fun failed()
      }
      
      object NetUtil: getDataField(), getDataSuccess{
          override fun success() {
              println("success")
          }
      
          override fun failed() {
              println("failed")
          }
      
          val state: Int = 0
          fun getData(): String = "请求成功"
      }
    

3. 伴生对象和静态成员

相当于java中的静态方法

  • 每个类可以对应一个伴生对象

  • 伴生对象的成员全局独一份

  • 如果java中想直接调用kotlin中的静态方法或者静态变量,可以考虑使用JvmField JvmStatic.

      open class Util private constructor(var anInt: Int) {
          companion object {
              @JvmStatic
              fun plus(first: Int, second: Int) = first + second
      
              fun copy(util: Util) = Util(util.anInt)
              @JvmField
              val tag = "tag"
          }
      }
    

4. 方法的重载

通过给方法的参数配置默认值,即可实现方法的重载,按理说,一切可以拥有默认值的方法重载才是合理的方法重载。

名称形同、参数不同,跟返回值没有关系

class OverLoadTest {
    @JvmOverLoads
    fun a(anInt: Int = 0, string: String="") = 1
}

val test = OverLoadTest()
test.a(1, "")
test.a()
test.a(anInt = 2)
test.a(string = "")

使用JvmOverLoads是为了方便Java中调用方法的重载.

5. 扩展方法

kotlin中的扩展方法,我认为相当于java中的代理模式,拿到被代理的对象,然后进行一系列的操作。

fun String.add(anInt: Int): String {
    var sb = StringBuilder()
    for (i in 0 until anInt) {
        sb.append(this)
    }
    return sb.toString()
}

operator fun String.times(anInt: Int): String {
    var sb = StringBuilder()
    for (i in 0 until anInt) {
        sb.append(this)
    }
    return sb.toString()
}

// 使用
var string = "xiaweizi"
println(string.add(5))
println(string * (3))

6. 属性代理

类似之前说的var anInt: Int by lazy{2},懒赋值就是使用的属性代理,来看个例子:

fun main(args: Array<String>) {
    val a: Int by DelegatesTest()
    println(a)

    var b: Int by DelegatesTest()
    b = 3
    println(b)
}

class DelegatesTest {
    private var anInt: Int? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        println("getValue")
        return anInt?:0
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int): Unit {
        println("setValue")
        this.anInt = value
    }
}

val 对应 getValuevar对应getValue和setValue方法,这个时候声明的属性就全权交付给DelegatesTest类中的anInt代理,当anInt为空的时候返回0,否则返回anInt.

7. JavaBean

使用data修饰类,类似java中的javaBean,默认实现了set get toString等方法,并拥有componentN方法.

不过有个缺点就是,无法被继承,没有无参构造函数,可以通过安装allOpennoArg插件解决这个问题.

data class UserBean(var name: String, var age: Int)

val userBean: UserBean = UserBean("小芳", 23)
println(userBean.name)
println(userBean.toString())

println(userBean.component1())
println(userBean.component2())

val (name, age) = userBean
println("name: $name")
println("age: $age")

至于这种写法val (name, age) = userBean,是因为定义了component1的运算符

class Complex{
    operator fun component1() = "你好呀"
    operator fun component2() = 2
    operator fun component3() = 'a'
}

val complex = Complex()
val (a, b, c) = complex
println(a + b + c)

使用起来也是很简单的

8. 内部类

  • 定义在类内部的类
  • 与类成员有相似的访问控制
  • 默认是静态内部类,非静态用 inner 关键字
  • this@Outter this@Inner 的用法
  • 匿名内部类
    • 没有定义名字的内部类
    • 类名编译时生成,类似Outter$1.class
    • 可继承父类,实现多个接口,与Java注意区别

举个例子:

class Outer{
    var string: String = "outer"
    class Inner1{
        var string: String = "inner1"
        fun sum(first: Int, second: Int) = first + second
    }

    inner class Inner2{
        var string: String = "inner2"
        fun cha(first: Int, second: Int) = first - second
        fun getInnerField() = this.string
        fun getOuterField() = this@Outer.string
    }
}

fun main(args: Array<String>) {
    val inner1 = Outer.Inner1()
    val inner2 = Outer().Inner2()

    println(inner1.sum(1, 2))

    println(inner2.cha(2, 1))
    println(inner2.getInnerField())
    println(inner2.getOuterField())
}

匿名内部类:

val listener: onClickListener = object : Father(), Mother, onClickListener{
    override fun sing() {
        println("mother sing")
    }

    override fun teach() {
        println("father teach")
    }

    override fun onClick() {
        println("匿名内部类")
    }
}

使用Object实现匿名内部类

9. 枚举和密封类

枚举是对象可数,每个状态相当于每个对象,是可以传构造参数的

密封类时子类可数,在kotlin大于1.1子类只需要与密封类在同一个文件加,保护子类的位置

sealed class SealedClassTest{
    class sum(first: Int, seocnd: Int): SealedClassTest()
    class cha(first: Int, seocnd: Int): SealedClassTest()

    object Bean: SealedClassTest()
}

enum class HttpStatus(val anInt: Int){
    SUCCESS(0), FAILED(1), LOADING(2)
}

fun main(args: Array<String>) {
    val class1 = SealedClassTest.cha(1, 2)
    println(HttpStatus.SUCCESS)
}

高阶函数

1. 基本概念

  • 传入或者返回函数的函数
  • 函数引用 ::println
  • 带有Receiver的引用 pdfPrinter::println

有三种显示

// 1. 包级函数
intArray.forEach(::print)

// 2. 类.方法
intArray.forEach(Int::addOne)
fun Int.addOne(): Unit {
    println("addOne:$this")
}

// 3. 对象.方法
intArray.forEach(AddTwo()::addTwo)
class AddTwo {
    fun addTwo(anInt: Int): Unit {
        println("addTwo:$anInt")
    }
}

2. 常用的高阶函数

常用的高阶函数还是有很多的,会简单的使用例子即可:

// 遍历
fun forEachTest() {
    val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")

    strings.forEach { println(it) } // 遍历每一个值
    strings.forEachIndexed { index, s -> println("index:$index,String:$s") } // 遍历 下标和值一一对应

}

// 重新拷贝一个值
fun mapTest() {
    val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")
    var map = strings.map { "$it-test" }
    map.forEach { print("$it\t") }
}

// 将集合合体
fun flatMapTest() {
    val lists = listOf(1..10,
            2..11,
            3..12)

    var flatMap = lists.flatMap {
        it.map {
            "No.$it"
        }
    }
    flatMap.forEach(::println)
}

fun reduceTest() {
    val ints = listOf(2, 3, 4, 5)
    println(ints.reduce { acc, i ->
        acc + i
    })
}

// 字符串连接
fun foldTest(){
    val ints = listOf(2, 3, 4, 5)
    println(ints.fold(StringBuffer(), { acc, i -> acc.append("$i,") }))
    println(ints.joinToString(","))
}

fun filterTest() {
    val ints = listOf(1, 2, 3, 4, 5, 6)
    println(ints.filter { element -> element % 2 == 0 })
}

// 当值不是奇数就去,遇到偶数就停止了
fun takeWhileTest() {
    val ints = listOf(1, 3, 3, 4, 5, 6)
    println(ints.takeWhile { it % 2 != 0 })
}

fun letTest() {
    findPerson()?.let { (name, age) -> println("name:$name, age:$age") }
    findPerson()?.apply { println("name:$name, age:$age") }
    with(findPerson()!!) { println("name:$name, age:$age") }
}

data class Person(val name: String, val age: Int)

fun findPerson(): Person? {
    return Person("aa", 23)
}

3. 复合函数

有点类似数据中的f(g(x))

fun main(args: Array<String>) {
    val add1 = {int: Int ->
        println("add1")
        int + 1}
    val add2 = {int : Int ->
        println("add2")
        int + 2}
    var add3 = add1 addThen (add2)
    println(add3(4))
}


infix fun <P1, P2, R> Function1<P1, P2>.addThen(function: Function1<P2, R>): Function1<P1, R> {
    return fun(p: P1): R{
        return function.invoke(this.invoke(p))
    }
}

4. Currying

简单来说就是多元函数变换成一元函数调用链式,举个简单的例子,这是优化之前:

fun log(tag: String, out: OutputStream, message: String){
    out.write("[$tag], $message".toByteArray())
}

优化之后

fun log(tag: String)
    = fun(out: OutputStream)
    = fun(message: String)
    = out.write("[$tag], $message".toByteArray())

5. 计算文件字符串个数的小例子

首先将字符串转换成字符串数组:

val map: HashMap<Char, Int> = HashMap()
var toCharArray = File("build.gradle").readText().toCharArray()

通过分组的方式,统计每个字符串的个数,并打印:

toCharArray.groupBy { it }.map { it.key to  it.value.size }.forEach { println(it) }

kotlinjava的混合开发

1. 基本的交互操作

属性读写

  • Kotlin自动识别 Java Getter/Setter
  • Java操作Kotlin属性通过Getter/Setter

空安全类型

  • Kotlin空安全类型的原理
  • 平台类型Platform Type
  • Java可以通过@Nullable、@NotNull

几类函数的调用

  • 包级函数:静态方法
  • 扩展方法:带Receiver的静态方法
  • 运算符重载:带Receiver的对应名称的静态方法

几个常用的注解

  • @JvmField:将属性编译为Java变量
  • @JvmStatic:将对象的方法编译成功Java静态方法
  • @JvmOverloads:默认参数生成重载方法
  • @JvmName:制定Kotlin文件编译后的类名

NoArg 和 AllOpen

  • NoArg为被标注的类生成无参构造
  • AllOpen为被标注的类去掉final,允许被继承

正则表达式

  • Raw字符串定义正则表达式
  • JavaPattern
  • KotlinRegex

举个例子:

val source = "Hello This my phone number: 010-12345678."
val pattern = """.*(\d{3}-\d{8}).*"""

Regex(pattern).findAll(source).toList().flatMap(MatchResult::groupValues).forEach(::print)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容