Golang數(shù)據(jù)結(jié)構(gòu)之Interface

背景

除了了與基礎(chǔ)性能息息相關(guān)的網(wǎng)絡(luò)和內(nèi)存管理之外纳胧,Golang 給人印象最深的一個(gè)特性就是 Inerface 數(shù)據(jù)結(jié)構(gòu)了,Interface 距離業(yè)務(wù)系統(tǒng)非常近郊丛,其獨(dú)特的靜態(tài)編譯,動(dòng)態(tài)檢測(cè)的類(lèi)型定義方式為提供了非常好的編程靈活性,大大簡(jiǎn)化了業(yè)務(wù)系統(tǒng)設(shè)計(jì)的復(fù)雜程度坊夫。

概述

通過(guò) Interface 你可以像使用Python、JavaScript這類(lèi)動(dòng)態(tài)類(lèi)型那樣的完成對(duì)象的類(lèi)型動(dòng)態(tài)轉(zhuǎn)換撤卢,與此同時(shí)作為一門(mén)傳統(tǒng)編譯型語(yǔ)言环凿,在編譯過(guò)程中,編譯器會(huì)幫你找到程序中類(lèi)型不匹配的問(wèn)題放吩。

下面我們嘗試通過(guò)一個(gè)簡(jiǎn)單的例子智听,看看 Interface 如何使用:

// 首先,聲明一個(gè)擁有兩個(gè)函數(shù)的接口類(lèi)型 ReadCloser渡紫,以及一個(gè)接收 ReadCloser 接口類(lèi)型參數(shù)的函數(shù)
type ReadCloser interface {
    Read(b []byte) (n int, err os.Error)
    Close()
}

func ReadAndClose(r ReadCloser, buf []byte) (n int, err os.Error) {
    for len(buf) > 0 && err == nil {
        var nr int
        nr, err = r.Read(buf)
        n += nr
        buf = buf[nr:]
    }
    r.Close()
    return
}

// 接下來(lái)到推,然后聲明一個(gè)擁有一個(gè)函數(shù)的接口類(lèi)型 Stringer,以及一個(gè)接口 interface{} 接口類(lèi)型的函數(shù)
type Stringer interface {
    String() string
}

func ToString(any interface{}) string {
    if v, ok := any.(Stringer); ok {
        return v.String()
    }
    switch v := any.(type) {
    case int:
        return strconv.Itoa(v)
    case float:
        return strconv.Ftoa(v, 'g', -1)
    }
    return "???"
}

// 最后是測(cè)試代碼
type stringer struct {
    data string
}

func test1() {
    stringer s
    t := "hello world"
    ReadAndClose(s, t)
}

func test2() {
    stringer s
    ToString(s)
}

函數(shù) test1 中由于我們的 stringer 數(shù)據(jù)結(jié)構(gòu)并沒(méi)有實(shí)現(xiàn) Read 和 Close 函數(shù)惕澎,此處會(huì)引起編譯時(shí)的報(bào)錯(cuò)莉测,而 test2 中由于使用 interface{} 編譯器不會(huì)它為綁定任何靜態(tài)類(lèi)型檢測(cè),因此編譯不會(huì)出錯(cuò)唧喉,函數(shù)體中第一句是一個(gè)類(lèi)型斷言捣卤,如果 any 對(duì)象可以轉(zhuǎn)換成 Stringer 接口類(lèi)忍抽,則 ok 為 true;如果不能完成轉(zhuǎn)換董朝,則 ok 為 false鸠项。如果類(lèi)型轉(zhuǎn)換成功,則調(diào)用 String 函數(shù)并返回結(jié)果子姜,如果轉(zhuǎn)換失敗祟绊,則做一個(gè)類(lèi)型判斷,判斷 any 的類(lèi)型是否是 int 或者 float闲询,如果是則調(diào)用 strconv 將數(shù)值轉(zhuǎn)換成字符串久免,如果不是則返回 "???"。

編譯器是如何判斷 any 對(duì)象是否可以完成類(lèi)型轉(zhuǎn)換呢扭弧?

編譯器通過(guò)檢查 any 所對(duì)應(yīng)的函數(shù)表中是否存在 String 這個(gè)函數(shù)阎姥,如果存在則可以完成類(lèi)型轉(zhuǎn)換,如果不存在則無(wú)法完成鸽捻。

PS: 需要說(shuō)明的是 "switch v := any.(type)" 一般也成為 type-switch呼巴,中文翻譯為類(lèi)型分支,可以算作做 type assertion 的語(yǔ)法糖御蒲,每個(gè)分支會(huì)被編譯器解釋為一句包含 type assertion 的 case 語(yǔ)句衣赶,示例如下:

package main

import (
    "fmt"
    "strconv"
)

type Stringer interface {
    String() string
}

type Binary uint64

func (i Binary) String() string {
    return strconv.Uitob64(i.Get(), 2)
}

func (i Binary) Get() uint64 {
    return uint64(i)
}

func main() {
    b := Binary(200)
    s := Stringer(b)
    fmt.Println(s.String())
}

使用 Golang 提供的編譯工具轉(zhuǎn)換成 Plan9 匯編,截取 test1 函數(shù)的代碼段

go tool compile -S main.go
main.test1 STEXT size=261 args=0x10 locals=0x50 funcid=0x0 align=0x0
+   0x0000 00000 (main.go:7)    TEXT    main.test1(SB), ABIInternal, $80-16
+   0x0000 00000 (main.go:7)    CMPQ    SP, 16(R14)
+   0x0004 00004 (main.go:7)    PCDATA  $0, $-2
+   0x0004 00004 (main.go:7)    JLS 229
+   0x000a 00010 (main.go:7)    PCDATA  $0, $-1
+   0x000a 00010 (main.go:7)    SUBQ    $80, SP
+   0x000e 00014 (main.go:7)    MOVQ    BP, 72(SP)
+   0x0013 00019 (main.go:7)    LEAQ    72(SP), BP
+   0x0018 00024 (main.go:7)    MOVQ    AX, main.any+88(FP)
+   0x001d 00029 (main.go:7)    MOVQ    BX, main.any+96(FP)
+   0x0022 00034 (main.go:7)    FUNCDATA    $0, gclocals·IuErl7MOXaHVn7EZYWzfFA==(SB)
+   0x0022 00034 (main.go:7)    FUNCDATA    $1, gclocals·EXTrhv4b3ahawRWAszmcVw==(SB)
+   0x0022 00034 (main.go:7)    FUNCDATA    $2, main.test1.stkobj(SB)
+   0x0022 00034 (main.go:7)    FUNCDATA    $5, main.test1.arginfo1(SB)
+   0x0022 00034 (main.go:7)    FUNCDATA    $6, main.test1.argliveinfo(SB)
+   0x0022 00034 (main.go:7)    PCDATA  $3, $1
+   0x0022 00034 (main.go:8)    TESTQ   AX, AX
+   0x0025 00037 (main.go:8)    JEQ 219
+   0x002b 00043 (main.go:8)    MOVL    16(AX), DX
+   0x002e 00046 (main.go:8)    CMPL    DX, $1810709754
+   0x0034 00052 (main.go:8)    JNE 137
++  0x0036 00054 (main.go:9)    LEAQ    type.int32(SB), DX
+   0x003d 00061 (main.go:9)    NOP
+   0x0040 00064 (main.go:9)    CMPQ    AX, DX
+   0x0043 00067 (main.go:9)    JNE 219
+   0x0049 00073 (main.go:10)   MOVUPS  X15, main..autotmp_17+56(SP)
++  0x004f 00079 (main.go:10)   LEAQ    type.string(SB), DX
+   0x0056 00086 (main.go:10)   MOVQ    DX, main..autotmp_17+56(SP)
+   0x005b 00091 (main.go:10)   LEAQ    main..stmp_0(SB), DX
+   0x0062 00098 (main.go:10)   MOVQ    DX, main..autotmp_17+64(SP)
+   0x0067 00103 (<unknown line number>)    NOP
+   0x0067 00103 ($GOROOT/src/fmt/print.go:294) MOVQ    os.Stdout(SB), BX
+   0x006e 00110 ($GOROOT/src/fmt/print.go:294) LEAQ    go.itab.*os.File,io.Writer(SB), AX
+   0x0075 00117 ($GOROOT/src/fmt/print.go:294) LEAQ    main..autotmp_17+56(SP), CX
+   0x007a 00122 ($GOROOT/src/fmt/print.go:294) MOVL    $1, DI
+   0x007f 00127 ($GOROOT/src/fmt/print.go:294) MOVQ    DI, SI
+   0x0082 00130 ($GOROOT/src/fmt/print.go:294) PCDATA  $1, $1
+   0x0082 00130 ($GOROOT/src/fmt/print.go:294) CALL    fmt.Fprintln(SB)
+   0x0087 00135 (main.go:8)    JMP 219
+   0x0089 00137 (main.go:8)    CMPL    DX, $-1920832363
+   0x008f 00143 (main.go:8)    JNE 219
++  0x0091 00145 (main.go:11)   LEAQ    type.float32(SB), DX
+   0x0098 00152 (main.go:11)   CMPQ    AX, DX
+   0x009b 00155 (main.go:11)   JNE 219
+   0x009d 00157 (main.go:12)   MOVUPS  X15, main..autotmp_19+40(SP)
++  0x00a3 00163 (main.go:12)   LEAQ    type.string(SB), DX
+   0x00aa 00170 (main.go:12)   MOVQ    DX, main..autotmp_19+40(SP)
+   0x00af 00175 (main.go:12)   LEAQ    main..stmp_1(SB), DX
+   0x00b6 00182 (main.go:12)   MOVQ    DX, main..autotmp_19+48(SP)
+   0x00bb 00187 (<unknown line number>)    NOP
+   0x00bb 00187 ($GOROOT/src/fmt/print.go:294) MOVQ    os.Stdout(SB), BX
+   0x00c2 00194 ($GOROOT/src/fmt/print.go:294) LEAQ    go.itab.*os.File,io.Writer(SB), AX
+   0x00c9 00201 ($GOROOT/src/fmt/print.go:294) LEAQ    main..autotmp_19+40(SP), CX
+   0x00ce 00206 ($GOROOT/src/fmt/print.go:294) MOVL    $1, DI
+   0x00d3 00211 ($GOROOT/src/fmt/print.go:294) MOVQ    DI, SI
+   0x00d6 00214 ($GOROOT/src/fmt/print.go:294) CALL    fmt.Fprintln(SB)
+   0x00db 00219 (main.go:14)   PCDATA  $1, $-1
+   0x00db 00219 (main.go:14)   MOVQ    72(SP), BP
+   0x00e0 00224 (main.go:14)   ADDQ    $80, SP
+   0x00e4 00228 (main.go:14)   RET
+   0x00e5 00229 (main.go:14)   NOP
+   0x00e5 00229 (main.go:7)    PCDATA  $1, $-1
+   0x00e5 00229 (main.go:7)    PCDATA  $0, $-2
+   0x00e5 00229 (main.go:7)    MOVQ    AX, 8(SP)
+   0x00ea 00234 (main.go:7)    MOVQ    BX, 16(SP)
+   0x00ef 00239 (main.go:7)    CALL    runtime.morestack_noctxt(SB)
+   0x00f4 00244 (main.go:7)    MOVQ    8(SP), AX
+   0x00f9 00249 (main.go:7)    MOVQ    16(SP), BX
+   0x00fe 00254 (main.go:7)    PCDATA  $0, $-1
+   0x00fe 00254 (main.go:7)    NOP
+   0x0100 00256 (main.go:7)    JMP 0

// 省略后面的非函數(shù)聲明部分的源碼
// ...

可以看到四行 type assertion 被標(biāo)記出來(lái)了(行首標(biāo)記為++)厚满。

回到類(lèi)型斷言的問(wèn)題來(lái)府瞄,接下來(lái)看一個(gè)簡(jiǎn)單的例子。

type Binary uint64

var _ Stringer = (*Binary)(nil)

func (i Binary) String() string {
    return strconv.Uitob64(i.Get(), 2)
}

func (i Binary) Get() uint64 {
    return uint64(i)
}

如果我們定義一個(gè) Binary 類(lèi)型的變量將其傳入 ToString() 函數(shù)時(shí)碘箍,由于我們?yōu)?Binary 類(lèi)型定義了 String() 函數(shù)遵馆,因此 any.(Stringer) 可以將 Binary 轉(zhuǎn)換成 Stringer 接口類(lèi)型。這就是我們說(shuō)的 Golang 接口可以實(shí)現(xiàn) ”鴨子類(lèi)型“ 的威力丰榴,我們無(wú)需顯示的聲明接口類(lèi)型货邓,編譯器會(huì)通過(guò)接口比對(duì)的方式為我們校驗(yàn)類(lèi)型是否匹配。需要主要是工程上四濒,Golang 鴨子類(lèi)型雖然有靈活性的優(yōu)點(diǎn)换况,但是一般跨包或是項(xiàng)目去實(shí)現(xiàn)某個(gè)接口時(shí),程序會(huì)變的可讀性非常差并且極容器出錯(cuò)盗蟆,那我們能不能像 Java 中的接口一樣戈二,讓我們知道一個(gè)接口中我們需要實(shí)現(xiàn)具體定義哪些函數(shù),類(lèi)似于”接口繼承”的語(yǔ)法喳资。

Interface 實(shí)現(xiàn)

Golang 的類(lèi)型設(shè)計(jì)原則中挽拂,一般包含 type 和 value 兩部分, Interface 的實(shí)現(xiàn)也遵循這個(gè)原則骨饿,不過(guò)亏栈,golang 編譯器會(huì)根據(jù) interface 是否包含有 method,實(shí)現(xiàn)上用兩種不同數(shù)據(jù)結(jié)構(gòu)來(lái):一種是有 method 的 interface 對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)為 iface宏赘;一種是沒(méi)有 method 的 empty interface 對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)為 eface绒北。

// eface 數(shù)據(jù)結(jié)構(gòu)
type eface struct {
    _type *_type                // 類(lèi)型信息
    data  unsafe.Pointer        // 原數(shù)據(jù)存放的位置
}

// 大多數(shù)的 Golang 中的數(shù)據(jù)結(jié)構(gòu)其底層都會(huì)對(duì)應(yīng)一種 _type 類(lèi)型的數(shù)據(jù)結(jié)構(gòu)
type _type struct {
    size       uintptr // type size
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32  // hash of type; avoids computation in hash tables
    tflag      tflag   // extra type information flags
    align      uint8   // alignment of variable with this type
    fieldalign uint8   // alignment of struct field with this type
    kind       uint8   // enumeration for C
    alg        *typeAlg  // algorithm table
    gcdata    *byte    // garbage collection data
    str       nameOff  // string form
    ptrToThis typeOff  // type for pointer to this type, may be zero
}

// iface 數(shù)據(jù)結(jié)構(gòu)
type iface struct {
    tab  *itab              // 可以理解為含有接口函數(shù)表的類(lèi)型信息
    data unsafe.Pointer     // 原數(shù)據(jù)存放的位置
}

type itab struct {
    inter  *interfacetype       // interface 類(lèi)型信息
    _type  *_type               // 原數(shù)據(jù)結(jié)構(gòu)的類(lèi)型信息
    link   *itab
    bad    int32
    inhash int32      // 只有 itab 被擁有記錄在了 hash 表中
    fun    [1]uintptr // 函數(shù)表入口指針
}

// 函數(shù)名聲明
type imethod struct {
    name nameOff
    ityp typeOff
}

type interfacetype struct {
    typ     _type                   // interface type
    pkgpath name                    // 包路徑
    mhdr    []imethod               // 接口函數(shù)名聲明表
}

需要說(shuō)明:

  1. 對(duì)于非空的接口,其對(duì)應(yīng)的類(lèi)型有兩個(gè)察署,一般稱(chēng)為 interface type 和 concrete type闷游, interface type 保存在 itab.inter.typ 中,concrete type 保存在 itab._type 中贴汪。
  2. itab 中存放了兩張函數(shù)表脐往,一張表對(duì)應(yīng)接口對(duì)應(yīng)實(shí)際接收到的函數(shù),通過(guò) itab.fun[0] 指向第一個(gè)函數(shù)對(duì)應(yīng)的函數(shù)指針扳埂,后續(xù)函數(shù)根據(jù)函數(shù)名的字典值有小到大的排列业簿,與C++中對(duì)象的虛函數(shù)表及虛函數(shù)的定義方式非常類(lèi)似,另外一張表對(duì)應(yīng)接口定制是函數(shù)聲明阳懂。

下面通過(guò)一段代碼樣例梅尤,展示 Golang 如何實(shí)現(xiàn) interface 對(duì)象的創(chuàng)建及類(lèi)型轉(zhuǎn)換。

package main

import (
    "fmt"
    "strconv"
)

type Stringer interface {
    String() string
}

type Binary uint64

func (i Binary) String() string {
    return strconv.Uitob64(i.Get(), 2)
}

func (i Binary) Get() uint64 {
    return uint64(i)
}

func main() {
1    b := Binary(200)
2    var a interface{} = b
3    s := Stringer(b)
4    fmt.Println(s.String())
}

main 函數(shù)代碼第一行岩调,由于 Binary 沒(méi)有實(shí)現(xiàn)任何函數(shù)時(shí)巷燥,因此 對(duì)象 b 只是一個(gè)普通的數(shù)據(jù)結(jié)構(gòu),其對(duì)應(yīng)的內(nèi)存區(qū)域存放對(duì)應(yīng)的 uint64 數(shù)值号枕。

Binary

代碼第二行缰揪,嘗試創(chuàng)建一個(gè)空接口對(duì)象,然后將 Binary 對(duì)象賦值給它葱淳,編譯器會(huì)構(gòu)造一個(gè) eface 類(lèi)型的數(shù)據(jù)結(jié)構(gòu)钝腺,然后將 Binary 對(duì)應(yīng)的 _type 以及 Binary 對(duì)應(yīng)的數(shù)據(jù)保存下來(lái)。

空接口

代碼第三行蛙紫,創(chuàng)建一個(gè) Stringer 類(lèi)型的接口對(duì)象拍屑,然后將 Binary 對(duì)象賦值給它,編譯器會(huì)構(gòu)造一個(gè) iface 類(lèi)型的數(shù)據(jù)結(jié)構(gòu)坑傅,itab中分別保存 Binary 和 Stringer 對(duì)應(yīng)的 _type 以及 定義了 Binary 作為接收器的函數(shù)指針僵驰。

Stringer

類(lèi)型斷言(Type Assertion)

根據(jù)前面看過(guò)了的例子,我們知道類(lèi)型斷言的語(yǔ)句會(huì)被替換成 runtime 包中的 assert 函數(shù)唁毒,那我們把這兩個(gè)函數(shù)的源碼貼出來(lái)蒜茴,需要說(shuō)明一下 assertI2I 是 iface 類(lèi)型的接口類(lèi)型斷言對(duì)應(yīng)的函數(shù),assertE2I 是 eface 類(lèi)型的接口類(lèi)型斷言對(duì)應(yīng)的函數(shù)浆西。

類(lèi)型斷言的工作其實(shí)非常簡(jiǎn)單粉私,先判斷 itab.inter 這個(gè)接口類(lèi)型是否相同,如果相同直接返回近零,如果不同則進(jìn)入 getitab 進(jìn)行處理诺核,重點(diǎn)看一下這個(gè)函數(shù)抄肖。

func assertI2I(inter *interfacetype, tab *itab) *itab {
    if tab == nil {
        // explicit conversions require non-nil interface value.
        panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
    }
    if tab.inter == inter {
        return tab
    }
    // 
    return getitab(inter, tab._type, false)
}

func assertI2I2(inter *interfacetype, i iface) (r iface) {
    tab := i.tab
    if tab == nil {
        return
    }
    if tab.inter != inter {
        tab = getitab(inter, tab._type, true)
        if tab == nil {
            return
        }
    }
    r.tab = tab
    r.data = i.data
    return
}

func assertE2I(inter *interfacetype, t *_type) *itab {
    if t == nil {
        // explicit conversions require non-nil interface value.
        panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
    }
    return getitab(inter, t, false)
}

func assertE2I2(inter *interfacetype, e eface) (r iface) {
    t := e._type
    if t == nil {
        return
    }
    tab := getitab(inter, t, true)
    if tab == nil {
        return
    }
    r.tab = tab
    r.data = e.data
    return
}

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
    // _, ok := a.(interface{}) 這樣的空接口斷言直接拋出錯(cuò)誤。
    if len(inter.mhdr) == 0 {
        throw("internal error - misuse of itab")
    }

    // 判斷傳入類(lèi)型是否為 Uncomon type
    // Golang 類(lèi)型定義這里就不展開(kāi)做詳細(xì)的講解了窖杀,Uncommon Type可以簡(jiǎn)單理解為一個(gè)綁定了 Methods 的數(shù)據(jù)結(jié)構(gòu)漓摩。
    if typ.tflag&tflagUncommon == 0 {
        if canfail {
            return nil
        }
        name := inter.typ.nameOff(inter.mhdr[0].name)
        panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
    }

    var m *itab

    // itabTable 是編譯器創(chuàng)建的 itab 緩存哈希表,先通過(guò)原子操作查表找到 itab 如果沒(méi)有找到入客,則再通過(guò)加鎖方式查找一遍
    t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
    if m = t.find(inter, typ); m != nil {
        goto finish
    }

    // Not found.  Grab the lock and try again.
    lock(&itabLock)
    if m = itabTable.find(inter, typ); m != nil {
        unlock(&itabLock)
        goto finish
    }

    // 經(jīng)過(guò)兩次查表都沒(méi)有找到 itab 對(duì)應(yīng)的類(lèi)型管毙,就創(chuàng)建一個(gè)新的 itab 對(duì)象,并將其存入 itabTable 中
    m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))
    m.inter = inter
    m._type = typ
    m.hash = 0
    m.init()
    itabAdd(m)
    unlock(&itabLock)
finish:
    if m.fun[0] != 0 {
        return m
    }
    if canfail {
        return nil
    }
    // this can only happen if the conversion
    // was already done once using the , ok form
    // and we have a cached negative result.
    // The cached result doesn't record which
    // interface function was missing, so initialize
    // the itab again to get the missing function name.
    panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

更多技術(shù)分享瀏覽我的博客:

https://thierryzhou.github.io

參考

本文由mdnice多平臺(tái)發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末桌硫,一起剝皮案震驚了整個(gè)濱河市夭咬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铆隘,老刑警劉巖卓舵,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異咖驮,居然都是意外死亡边器,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén)托修,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)忘巧,“玉大人,你說(shuō)我怎么就攤上這事睦刃⊙庾欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵涩拙,是天一觀(guān)的道長(zhǎng)际长。 經(jīng)常有香客問(wèn)我,道長(zhǎng)兴泥,這世上最難降的妖魔是什么工育? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮搓彻,結(jié)果婚禮上如绸,老公的妹妹穿的比我還像新娘。我一直安慰自己旭贬,他們只是感情好怔接,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著稀轨,像睡著了一般扼脐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奋刽,一...
    開(kāi)封第一講書(shū)人閱讀 52,874評(píng)論 1 314
  • 那天瓦侮,我揣著相機(jī)與錄音艰赞,去河邊找鬼。 笑死脏榆,一個(gè)胖子當(dāng)著我的面吹牛猖毫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播须喂,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼趁蕊!你這毒婦竟也來(lái)了坞生?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掷伙,失蹤者是張志新(化名)和其女友劉穎是己,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體任柜,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卒废,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宙地。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摔认。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宅粥,靈堂內(nèi)的尸體忽然破棺而出参袱,到底是詐尸還是另有隱情,我是刑警寧澤秽梅,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布抹蚀,位于F島的核電站,受9級(jí)特大地震影響企垦,放射性物質(zhì)發(fā)生泄漏环壤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一钞诡、第九天 我趴在偏房一處隱蔽的房頂上張望郑现。 院中可真熱鬧,春花似錦臭增、人聲如沸懂酱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)列牺。三九已至,卻和暖如春拗窃,著一層夾襖步出監(jiān)牢的瞬間瞎领,已是汗流浹背泌辫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留九默,地道東北人震放。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驼修,于是被迫代替她去往敵國(guó)和親殿遂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容