聊一聊Kotlin函數(shù)

可以到掘金,效果更好
聊一聊Kotlin函數(shù)

一直想學(xué)習(xí)下Kotlin,畢竟已經(jīng)被Android官方宣布kotln first的存在。

有些小伙伴可能會(huì)擔(dān)心畢竟是一門新的語言殖熟,難免存在一些Bug,其實(shí)我覺得這個(gè)擔(dān)心有點(diǎn)多余斑响, Kotlin語言項(xiàng)目開始于2010年菱属,到現(xiàn)在已經(jīng)9年時(shí)間了钳榨,而且還是由全球最嚴(yán)謹(jǐn)?shù)囊慌绦騿T開發(fā)的產(chǎn)品,有什么理由不相信它呢纽门?另外也有很多大公司在生產(chǎn)環(huán)境使用了薛耻,比如百度,Jetbrain(沒錯(cuò)就是開發(fā)Kotlin的公司),facebook等赏陵。

再去翻Android 官方文檔的時(shí)候饼齿,發(fā)現(xiàn)提供的示例代碼已經(jīng)變成了 Kotlin

逛全球最大同性交友網(wǎng)站(GitHub)也發(fā)現(xiàn)越來越多的開源庫都是基于 Kotlin 語言蝙搔。

今天先看下 Kotlin中的函數(shù)缕溉。

1.表達(dá)式函數(shù)體

通過下面這個(gè)簡單的例子看下函數(shù)聲明相關(guān)的概念,函數(shù)聲明的關(guān)鍵字是fun吃型,嗯证鸥,比JSfunction還簡單。

Kotlin中參數(shù)類型是放在變量:后面勤晚,函數(shù)返回類型也是敌土。

fun max(a: Int, b: Int) : Int {
    if (a > b) {
        return a 
    } else {
        return b
    }
}

當(dāng)然, Kotlin是有類型推導(dǎo)功能运翼,如果可以根據(jù)函數(shù)表達(dá)式推導(dǎo)出類型,也可以不寫返回類型兴枯。

但是上面的還是有點(diǎn)繁瑣血淌,還能再簡單,在 Kotlin中if是表達(dá)式财剖,也就是有返回值的悠夯,因此可以直接return,另外判斷式中只有一行一句也可以省略掉大括號(hào):

fun max(a: Int, b: Int)  {
    return if (a > b) a else b
}

還能在簡單點(diǎn)嗎?可以躺坟,if是表達(dá)式沦补,那么就可以通過表達(dá)式函數(shù)體返回:

fun max(a: Int, b: Int)  = if(a > b)  a else b

最終只需要一行代碼。

Example

再看下面這個(gè)例子咪橙,后面會(huì)基于這個(gè)例子進(jìn)行修改夕膀。這個(gè)函數(shù)把集合以某種格式輸出,而不是默認(rèn)的toString().

<T>是泛型美侦,在這里形參集合中的元素都是T類型产舞。返回String類型。

fun <T> joinToString(
        collection: Collection<T>,
        separator: String,
        prefix: String,
        postfix: String
): String {
    val sb = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) sb.append(separator)
        sb.append(element)
    }

    sb.append(postfix)
    return sb.toString()
}

2.命名參數(shù)調(diào)用

先來看下函數(shù)調(diào)用菠剩,相比Java, Kotlin中可以類似于JavaScript中帶命名參數(shù)進(jìn)行調(diào)用易猫,而且可以不用按函數(shù)聲明中的順序進(jìn)行調(diào)用,可以打亂順序具壮,比如下面:

joinToString(separator = " ", collection = list, postfix = "}", prefix = "{")

// example
val list = arrayListOf("10", "11", "1001")
println(joinToString(separator = " ", collection = list, postfix = "}", prefix = "{"))

>>> {10 11 1001}

3.默認(rèn)參數(shù)值

Java里面有重載這一說准颓,或者JavaScript有默認(rèn)參數(shù)值這一說哈蝇,Kotlin采用了默認(rèn)參數(shù)值。調(diào)用的時(shí)候就不需要給有默認(rèn)參數(shù)值的形參傳實(shí)參攘已。上面的函數(shù)改成如下:

fun <T> joinToString(
        collection: Collection<T>,
        separator: String = " ",
        prefix: String = "[",
        postfix: String = "]"
): String {
    ...
}

// 
joinToString(list)

那么調(diào)用的時(shí)候如果默認(rèn)參數(shù)值自己的滿足要求炮赦,就可以只傳入集合list即可。

4.頂層函數(shù)

不同于Java中函數(shù)只能定義在每個(gè)類里面贯被,Kotlin采用了JavaScript種的做法眼五,可以在文件任意位置處定義函數(shù),這種函數(shù)稱為頂層函數(shù)彤灶。

編譯后頂層函數(shù)會(huì)成為文件類下的靜態(tài)函數(shù)看幼,比如在文件名是join.kt下定義的joinToString函數(shù)可以通過JoinKt.joinToSting調(diào)用,其中JoinKt是編譯后的類名幌陕。

// 編譯成靜態(tài)函數(shù)
// 文件名 join.kt
package strings
fun joinToString() : String {...}

/* Java */
import strings.JoinKt;
JoinKt.joinToSting(....)

看下上面函數(shù)編譯后的效果:

// 編譯成class文件后反編譯結(jié)果
@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix, @NotNull String postfix) {
      Intrinsics.checkParameterIsNotNull(collection, "collection");
      Intrinsics.checkParameterIsNotNull(separator, "separator");
      Intrinsics.checkParameterIsNotNull(prefix, "prefix");
      Intrinsics.checkParameterIsNotNull(postfix, "postfix");
      StringBuilder sb = new StringBuilder(prefix);
      int index = 0;

      for(Iterator var7 = ((Iterable)collection).iterator(); var7.hasNext(); ++index) {
         Object element = var7.next();
         if (index > 0) {
            sb.append(separator);
         }

         sb.append(element);
      }

      sb.append(postfix);
      String var10000 = sb.toString();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "sb.toString()");
      return var10000;
   }

// 默認(rèn)函數(shù)值
public static String joinToString$default(Collection var0, String var1, String var2, String var3, int var4, Object var5) {
      if ((var4 & 2) != 0) {
         var1 = " ";
      }

      if ((var4 & 4) != 0) {
         var2 = "[";
      }

      if ((var4 & 8) != 0) {
         var3 = "]";
      }

      return joinToString(var0, var1, var2, var3);
}

接下來看下Kotlin中很重要的一個(gè)特性诵姜,擴(kuò)展函數(shù)。

5.擴(kuò)展函數(shù)

擴(kuò)展函數(shù)是類的一個(gè)成員函數(shù)搏熄,不過定義在類的外面

擴(kuò)展函數(shù)不能訪問私有的或者受保護(hù)的成員

擴(kuò)展函數(shù)也是編譯成靜態(tài)函數(shù)

所以可以在Java庫的基礎(chǔ)上通過擴(kuò)展函數(shù)進(jìn)行封裝棚唆,假裝好像都是在調(diào)用Kotlin自己的庫一樣,在KotlinCollection就是這么干的心例。

再對上面的joinToString來一個(gè)改造宵凌,終結(jié)版:

fun <T> Collection<T>.joinToString(
        separator: String = " ",
        prefix: String = "[",
        postfix: String = "]"
): String {
    val sb = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) sb.append(separator)
        sb.append(element)
    }

    sb.append(postfix)
    return sb.toString()
}

在這里聲明成了Collection接口類的擴(kuò)展函數(shù),這樣就可以直接通過list進(jìn)行調(diào)用, 在擴(kuò)展函數(shù)里面照持购螅可以使用this瞎惫,這里的this就是指向接收者對象,在這里就是list译株。

val list = arrayListOf("10", "11", "1001")
println(list.joinToString())

>>> [10 11 1001]

經(jīng)常我們需要對代碼進(jìn)行重構(gòu)缆八,其中一個(gè)重要的措施就是減少重復(fù)代碼鞋既,在java中可以抽取出獨(dú)立的函數(shù),但這樣有時(shí)候?qū)φw結(jié)構(gòu)并不太好,Kotlin提供了局部函數(shù)來解決這個(gè)問題论泛。

6.局部函數(shù)

顧名思義烟瞧,局部函數(shù)就是可以在函數(shù)內(nèi)部定義函數(shù)溃卡。先看下沒有使用局部函數(shù)的一個(gè)例子西疤,這個(gè)例子先對傳進(jìn)來的用戶名和地址進(jìn)行校驗(yàn),只有都不為空的情況下才存進(jìn)數(shù)據(jù)庫:

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(
            "Can't save user ${user.id}: empty Name")
    }

    if (user.address.isEmpty()) {
        throw IllegalArgumentException(
            "Can't save user ${user.id}: empty Address")
    }

    // Save user to the database
}

上面有重復(fù)的代碼夯缺,就是對name和address的校驗(yàn)重復(fù)了始锚,只是入?yún)⒌牟煌虼丝梢猿槌鲆粋€(gè)校驗(yàn)函數(shù)喳逛,使用局部函數(shù)重寫:

fun saveUser(user: User) {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                    "Can't save user ${user.id}: empty $fieldName")
        }
    }

    validate(user.name, "Name")
    validate(user.address, "Address")
}

布局函數(shù)可以訪問所在函數(shù)中的所有參數(shù)和變量瞧捌。

如果不支持Lambda都不好意思稱自己是一門現(xiàn)代語言,來看看Kotlin中的表演。

7.Lambda表達(dá)式

lambda本質(zhì)上是可以傳遞給其他函數(shù)的一小段代碼姐呐,可以當(dāng)成值到處傳遞

Lambda表達(dá)式以左大括號(hào)開始殿怜,以右大括號(hào)結(jié)束,箭頭->分割成兩邊曙砂,左邊是入?yún)⑼访眨疫吺呛瘮?shù)體。

val sum = {x : Int, y : Int -> x + y}
println(sum(1, 2))

// 可以直接run
run { println(42)}

如果lambda表達(dá)式是函數(shù)調(diào)用的最后一個(gè)實(shí)參鸠澈,可以放到括號(hào)外邊柱告;

當(dāng)lambda是函數(shù)唯一實(shí)參時(shí),可以去掉調(diào)用代碼中的空括號(hào)

和局部變量一樣笑陈,如果lambda參數(shù)的類型可以被推導(dǎo)出來际度,就不需要顯示的指定

val people = listOf(User(1, "A", "B"), User(2, "C", "D"))
people.maxBy { it.id }

如果在函數(shù)內(nèi)部使用lambda,可以訪問這個(gè)函數(shù)的參數(shù),還有在lambda之前定義的局部變量

fun printProblemCounts(responses: Collection<String>) {
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach {
        if (it.startsWith("4")) {
            clientErrors++
        } else if (it.startsWith("5")) {
            serverErrors++
        }
    }
    println("$clientErrors client errors, $serverErrors server errors")
}

考慮這么一種情況涵妥,如果一個(gè)函數(shù)A接收一個(gè)函數(shù)類型參數(shù)乖菱,但是這個(gè)參數(shù)功能已經(jīng)在其它地方定義成函數(shù)B了,有一種辦法就是傳入一個(gè)lambda表達(dá)式給A蓬网,在這個(gè)表達(dá)式中調(diào)用B窒所,但是這樣就有點(diǎn)繁瑣了,有沒有可以直接拿到B的方式呢帆锋?

我都說了這么多了吵取,肯定是有了。锯厢。皮官。那就是成員引用

8.成員引用

  • 如果lambda剛好是函數(shù)或者屬性的委托,可以用成員引用替換
people.maxBy(User::id)

不管引用的是函數(shù)還是屬性哲鸳,都不要在成員引用的名稱后面加括號(hào)

  • 引用頂層函數(shù)
fun salute() = println("Salute!")
run(::salute)
  • 如果lambda要委托給一個(gè)接收多個(gè)參數(shù)的函數(shù),提供成員引用代替會(huì)非常方便:
fun sendEmail(person: Person, message: String) {
    println("message: $message")
}

val action = { person: Person, message: String ->
        sendEmail(person, message)
}
// action可以簡化如下
val action = ::sendEmail
// 
action(p, "HaHa")
  • 可以用 構(gòu)造方法引用 存儲(chǔ)或者延期執(zhí)行創(chuàng)建類實(shí)例的動(dòng)作盔憨,構(gòu)造方法的引用的形式是在雙冒號(hào)后指定類名稱:
data class Person(val name: String, val age: Int)
val createPerson = ::Person
val p = createPerson("Alice", 29)
  • 還可以用同樣的方式引用擴(kuò)展函數(shù)
fun Person.isAdult() = age>= 21
val predicate = Person::isAdult

不看點(diǎn)稍微底層的徙菠,就顯得不夠?qū)I(yè),逼格不夠郁岩,接下來稍微探究下lambda的原理婿奔。

9.Lambda表達(dá)式原理

Kotlin 1.0起,每個(gè)lambda表達(dá)式都會(huì)被編譯成一個(gè)匿名類问慎,除非它是一個(gè)內(nèi)聯(lián)lambda萍摊。后續(xù)版本計(jì)劃支持生成Java 8字節(jié)碼,一旦實(shí)現(xiàn)如叼,編譯器就可以避免為每一個(gè)lambda表達(dá)式都生成一個(gè)獨(dú)立的.class文件冰木。

如果lambda捕捉了變量,每個(gè)被捕捉的變量會(huì)在匿名類中有對應(yīng)的字段,而且每次調(diào)用都會(huì)創(chuàng)建一個(gè)這個(gè)匿名類的新實(shí)例踊沸。否則歇终,一個(gè)單例就會(huì)被創(chuàng)建。類的名稱由lambda聲明所在的函數(shù)名稱加上后綴衍生出來逼龟,這個(gè)例子中就是TestLambdaKt$main$1.class

// TestLambda.kt
package ch05

fun salute(callback: () -> Unit) = callback()

fun main(args: Array<String>) {
    salute { println(3) }
}

編譯后评凝,生成兩個(gè)文件

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2019/7/24     14:33           1239 TestLambdaKt$main$1.class
-a----        2019/7/24     14:35           1237 TestLambdaKt.class

先看下TestLambdaKt$main$1.class, 構(gòu)造一個(gè)靜態(tài)實(shí)例ch05.TestLambdaKt$main$1 INSTANCE,在類加載的時(shí)候進(jìn)行賦值腺律,同時(shí)繼承接口Function0,實(shí)現(xiàn)invoke方法:

final class ch05.TestLambdaKt$main$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
  minor version: 0
  major version: 50
  flags: ACC_FINAL, ACC_SUPER
  Constant pool:...
{
  public static final ch05.TestLambdaKt$main$1 INSTANCE;
    descriptor: Lch05/TestLambdaKt$main$1;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public java.lang.Object invoke();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #12                 // Method invoke:()V
         4: getstatic     #18                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
         7: areturn

  public final void invoke();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_3
         1: istore_1
         2: getstatic     #24                 // Field java/lang/System.out:Ljava/io/PrintStream;
         5: iload_1
         6: invokevirtual #30                 // Method java/io/PrintStream.println:(I)V
         9: return
      LineNumberTable:
        line 6: 0
        line 6: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lch05/TestLambdaKt$main$1;

  ch05.TestLambdaKt$main$1();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: iconst_0
         2: invokespecial #35                 // Method kotlin/jvm/internal/Lambda."<init>":(I)V
         5: return

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #2                  // class ch05/TestLambdaKt$main$1
         3: dup
         4: invokespecial #56                 // Method "<init>":()V
         7: putstatic     #58                 // Field INSTANCE:Lch05/TestLambdaKt$main$1;
        10: return
}

再看下另外一個(gè)類TestLambdaKt.class, 在main方法中傳入TestLambdaKt$main$1.INSTANCE給方法salute,在方法salute中調(diào)用接口方法invoke奕短,見上面。

public final class ch05.TestLambdaKt
  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
  ...
{
  public static final void salute(kotlin.jvm.functions.Function0<kotlin.Unit>);
    descriptor: (Lkotlin/jvm/functions/Function0;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: ldc           #10                 // String callback
         3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
         6: aload_0
         7: invokeinterface #22,  1           // InterfaceMethod kotlin/jvm/functions/Function0.invoke:()Ljava/lang/Object;
        12: pop
        13: return
      LineNumberTable:
        line 3: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0 callback   Lkotlin/jvm/functions/Function0;
    Signature: #7                           // (Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;)V
    RuntimeInvisibleParameterAnnotations:
      0:
        0: #8()

  public static final void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: ldc           #27                 // String args
         3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
         6: getstatic     #33                 // Field ch05/TestLambdaKt$main$1.INSTANCE:Lch05/TestLambdaKt$main$1;
         9: checkcast     #18                 // class kotlin/jvm/functions/Function0
        12: invokestatic  #35                 // Method salute:(Lkotlin/jvm/functions/Function0;)V
        15: return
      LineNumberTable:
        line 6: 6
        line 7: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  args   [Ljava/lang/String;
    RuntimeInvisibleParameterAnnotations:
      0:
        0: #8()
}

lambda內(nèi)部沒有匿名對象那樣的的this:沒有辦法引用到lambda轉(zhuǎn)換成的匿名類實(shí)例匀钧。從編譯器角度看翎碑,lambda是一個(gè)代碼塊不是一個(gè)對象,不能把它當(dāng)成對象引用榴捡。Lambda中的this引用指向的是包圍它的類杈女。

如果在lambda中要用到常規(guī)意義上this呢?這個(gè)就需要帶接收者的函數(shù)吊圾〈镆看下比較常用的兩個(gè)函數(shù)withapply.

10.with函數(shù)

直接上Kotlin的源碼,with在這里聲明成內(nèi)聯(lián)函數(shù)(后面找機(jī)會(huì)說)项乒, 接收兩個(gè)參數(shù)啰劲,在函數(shù)體里面對接收者調(diào)用lambda表達(dá)式。在lambda表達(dá)式里面可以通過this引用到這個(gè)receiver對象檀何。

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

看個(gè)例子:

fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z') {
         result.append(letter)
    }
    result.append("\nNow I know the alphabet!")
    return result.toString()
}

with改造蝇裤, 在with里面就不用顯示通過StringBuilder進(jìn)行append調(diào)用。

fun alphabet(): String {
    val result = StringBuilder()
    return with(result) {
        for (letter in  'A'..'Z') {
            append(letter)
        }
        append("\nNow I know the alphabet!")
        this.toString()
    }
}

// 再進(jìn)一步
fun alphabet() = with(StringBuilder()) {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
    toString()
}

with返回的值是執(zhí)行l(wèi)ambda代碼的結(jié)果频鉴,該結(jié)果是lambda中的最后一個(gè)表達(dá)式的值栓辜。如果想返回的是接收者對象,而不是執(zhí)行l(wèi)ambda的結(jié)果垛孔,需要用apply函數(shù)藕甩。

11.apply函數(shù)

apply函數(shù)幾乎和with函數(shù)一模一樣,唯一的區(qū)別就是apply始終返回作為實(shí)參傳遞給它的對象周荐,也就是接收者對象

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

apply被聲明稱一個(gè)擴(kuò)展函數(shù)狭莱,它的接收者變成了作為實(shí)參傳入的lambda的接收者

fun alphabet() = StringBuilder().apply {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
}.toString()

可以調(diào)用庫函數(shù)再簡化:

fun alphabet() = buildString {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
}

//
/**
 * Builds new string by populating newly created [StringBuilder] using provided [builderAction]
 * and then converting it to [String].
 */
@kotlin.internal.InlineOnly
public inline fun buildString(builderAction: StringBuilder.() -> Unit): String =
        StringBuilder().apply(builderAction).toString()

不能再往下寫了,要不太長會(huì)被人打概作,做個(gè)總結(jié)腋妙。

12.總結(jié)

今天只是說了Kotlin中關(guān)于函數(shù)的一點(diǎn)特性,當(dāng)然也沒講全讯榕,比如內(nèi)聯(lián)函數(shù)骤素,高階函數(shù)等匙睹,因?yàn)樵賹懴氯ヌL了,所以后面再補(bǔ)充谆甜。從上面幾個(gè)例子也能大概感受到Kotlin的務(wù)實(shí)作風(fēng)垃僚,提供了很多特性幫助開發(fā)者減少冗余代碼的編寫,可以提高效率规辱,也能減少異常谆棺,讓程序猿早點(diǎn)小班,永葆頭發(fā)烏黑靚麗罕袋。

參考:

  • Kotlin Action
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末改淑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子浴讯,更是在濱河造成了極大的恐慌朵夏,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榆纽,死亡現(xiàn)場離奇詭異仰猖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)奈籽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門饥侵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衣屏,你說我怎么就攤上這事躏升。” “怎么了狼忱?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵膨疏,是天一觀的道長。 經(jīng)常有香客問我钻弄,道長佃却,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任窘俺,我火速辦了婚禮饲帅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘批销。我一直安慰自己洒闸,他們只是感情好染坯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布均芽。 她就那樣靜靜地躺著,像睡著了一般单鹿。 火紅的嫁衣襯著肌膚如雪掀宋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音劲妙,去河邊找鬼湃鹊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛镣奋,可吹牛的內(nèi)容都是我干的币呵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼侨颈,長吁一口氣:“原來是場噩夢啊……” “哼余赢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哈垢,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤妻柒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后耘分,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體举塔,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年求泰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了央渣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拜秧,死狀恐怖痹屹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枉氮,我是刑警寧澤志衍,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站聊替,受9級(jí)特大地震影響楼肪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惹悄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一春叫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泣港,春花似錦暂殖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至坡氯,卻和暖如春晨横,著一層夾襖步出監(jiān)牢的瞬間洋腮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工手形, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啥供,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓库糠,卻偏偏與公主長得像伙狐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子瞬欧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355