Swift 中的值類型與引用類型

由于種種原因,簡書等第三方平臺博客不再保證能夠同步更新,歡迎移步 GitHub:https://github.com/kingcos/Perspective/。謝謝!

Value Type & Reference Type

  • Info:
  • macOS 10.12.2 Beta
  • Xcode 8.2 Beta
  • Swift 3.0

2016-11-28 Update: 調(diào)整了行文順序,并增加了一部分圖文。

前言

最近在學(xué)設(shè)計模式中,發(fā)現(xiàn) Swift 中的 struct,class 以及 enum 在一般的使用中能夠做到互相替換,因此探究其背后的邏輯就十分有必要。而這一問題又引出了 Swift 中的值類型和引用類型的區(qū)別。在網(wǎng)上搜尋一番,雖然也找到很多很棒的資料,不過有的有些過時,或是比較分散,因此總結(jié)一篇,以便自己加深印象,也方便與大家交流。

由于 Swift 中的 struct 為值類型,class 為引用類型,因此文中以這兩種類型為代表來具體闡述。

stack & heap

內(nèi)存(RAM)中有兩個區(qū)域,棧區(qū)(stack)和堆區(qū)(heap)。在 Swift 中,值類型,存放在棧區(qū);引用類型,存放在堆區(qū)。

class RectClass {
    var height = 0.0
    var width = 0.0
}

struct RectStruct {
    var height = 0.0
    var width = 0.0
}

var rectCls = RectClass()
var rectStrct = RectStruct()
stack & heap in RAM

值類型 & 引用類型

值類型(Value Type)

值類型,即每個實例保持一份數(shù)據(jù)拷貝。

在 Swift 中,典型的有 struct,enum,以及 tuple 都是值類型。而平時使用的 IntDoubleFloatStringArrayDictionarySet 其實都是用結(jié)構(gòu)體實現(xiàn)的,也是值類型。

Swift 中,值類型的賦值為深拷貝(Deep Copy),值語義(Value Semantics)即新對象和源對象是獨立的,當(dāng)改變新對象的屬性,源對象不會受到影響,反之同理。

值類型
struct CoordinateStruct {
    var x: Double
    var y: Double
}

var coordA = CoordinateStruct(x: 0, y: 0)
var coordB = coordA

coordA.x = 100.0
print("coordA.x -> \(coordA.x)")
print("coordB.x -> \(coordB.x)")

// coordA.x -> 100.0
// coordB.x -> 0.0

如果聲明一個值類型的常量,那么就意味著該常量是不可變的(無論內(nèi)部數(shù)據(jù)為 var/let)。

let coordC = CoordinateStruct(x: 0, y: 0)
// WRONG: coordC.x = 100.0

在 Swift 3.0 中,可以使用 withUnsafePointer(to:_:) 函數(shù)來打印值類型變量的內(nèi)存地址,這樣就能看出兩個變量的內(nèi)存地址并不相同。

withUnsafePointer(to: &coordA) { print("\($0)") }
withUnsafePointer(to: &coordB) { print("\($0)") }

// 0x000000011df6ec10
// 0x000000011df6ec20

在 Swift 中,雙等號(== & !=)可以用來比較變量存儲的內(nèi)容是否一致,如果要讓我們的 struct 類型支持該符號,則必須遵守 Equatable 協(xié)議。

extension CoordinateStruct: Equatable {
    static func ==(left: CoordinateStruct, right: CoordinateStruct) -> Bool {
        return (left.x == right.x && left.y == right.y)
    }
}

if coordA != coordB {
    print("coordA != coordB")
}

// coordA != coordB

引用類型(Reference Type)

引用類型,即所有實例共享一份數(shù)據(jù)拷貝。

在 Swift 中,class 和閉包是引用類型。引用類型的賦值是淺拷貝(Shallow Copy),引用語義(Reference Semantics)即新對象和源對象的變量名不同,但其引用(指向的內(nèi)存空間)是一樣的,因此當(dāng)使用新對象操作其內(nèi)部數(shù)據(jù)時,源對象的內(nèi)部數(shù)據(jù)也會受到影響。

引用類型
class Dog {
    var height = 0.0
    var weight = 0.0
}

var dogA = Dog()
var dogB = dogA

dogA.height = 50.0
print("dogA.height -> \(dogA.height)")
print("dogB.height -> \(dogB.height)")

// dogA.height -> 50.0
// dogB.height -> 50.0

如果聲明一個引用類型的常量,那么就意味著該常量的引用不能改變(即不能被同類型變量賦值),但指向的內(nèi)存中所存儲的變量是可以改變的。

let dogC = Dog()
dogC.height = 50

// WRONG: dogC = dogA

在 Swift 3.0 中,可以使用以下方法來打印引用類型變量指向的內(nèi)存地址。從中即可發(fā)現(xiàn),兩個變量指向的是同一塊內(nèi)存空間。

print(Unmanaged.passUnretained(dogA).toOpaque())
print(Unmanaged.passUnretained(dogB).toOpaque())

// 0x0000600000031380
// 0x0000600000031380

在 Swift 中,三等號(=== & !==)可以用來比較引用類型的引用(即指向的內(nèi)存地址)是否一致。也可以在遵守 Equatable 協(xié)議后,使用雙等號(== & !=)用來比較變量的內(nèi)容是否一致。

if (dogA === dogB) {
    print("dogA === dogB")
}
// dogA === dogB

if dogC !== dogA {
    print("dogC !== dogA")
}
// dogC !== dogA

extension Animal: Equatable {
    static func ==(left: Animal, right: Animal) -> Bool {
        return (left.height == right.height && left.weight == right.weight)
    }
}

if dogC == dogA {
    print("dogC == dogA")
}
// dogC == dogA

參數(shù) 與 inout

預(yù)備

定義一個 ResolutionStruct 結(jié)構(gòu)體,以及一個 ResolutionClass 類。這里為了方便打印對象屬性,ResolutionClass 類遵從了 CustomStringConvertible 協(xié)議。

struct ResolutionStruct {
    var height = 0.0
    var width = 0.0
}

class ResolutionClass: CustomStringConvertible {
    var height = 0.0
    var width = 0.0
    
    var description: String {
        return "ResolutionClass(height: \(height), width: \(width))"
    }
}

函數(shù)傳參

在 Swift 中,函數(shù)的參數(shù)默認為常量,即在函數(shù)體內(nèi)只能訪問參數(shù),而不能修改參數(shù)值。具體來說:

  1. 值類型作為參數(shù)傳入時,函數(shù)體內(nèi)部不能修改其值
  2. 引用類型作為參數(shù)傳入時,函數(shù)體內(nèi)部不能修改其指向的內(nèi)存地址,但是可以修改其內(nèi)部的變量值
func test(sct: ResolutionStruct) {
//    WRONG: sct.height = 1080
    
    var sct = sct
    sct.height = 1080
}

func test(clss: ResolutionClass) {
//    WRONG: clss = ResolutionClass()
    clss.height = 1080
    
    var clss = clss
    clss = ResolutionClass()
    clss.height = 1440
}

但是如果要改變參數(shù)值或引用,那么就可以在函數(shù)體內(nèi)部直接聲明同名變量,并把原有變量賦值于新變量,那么這個新的變量就可以更改其值或引用。那么在函數(shù)參數(shù)的作用域和生命周期是什么呢?我們來測試一下,定義兩個函數(shù),目的為交換內(nèi)部的 heightwidth

值類型

func swap(resSct: ResolutionStruct) -> ResolutionStruct {
    var resSct = resSct
    withUnsafePointer(to: &resSct) { print("During calling: \($0)") }
    
    let temp = resSct.height
    resSct.height = resSct.width
    resSct.width = temp
    
    return resSct
}

var iPhone4ResoStruct = ResolutionStruct(height: 960, width: 640)
print(iPhone4ResoStruct)
withUnsafePointer(to: &iPhone4ResoStruct) { print("Before calling: \($0)") }
print(swap(resSct: iPhone4ResoStruct))
print(iPhone4ResoStruct)
withUnsafePointer(to: &iPhone4ResoStruct) { print("After calling: \($0)") }

// ResolutionStruct(height: 960.0, width: 640.0)
// Before calling: 0x00000001138d6f50
// During calling: 0x00007fff5a512148
// ResolutionStruct(height: 640.0, width: 960.0)
// ResolutionStruct(height: 960.0, width: 640.0)
// After calling: 0x00000001138d6f50

小結(jié):在調(diào)用函數(shù)前后,外界變量值并沒有因為函數(shù)內(nèi)對參數(shù)的修改而發(fā)生變化,而且函數(shù)體內(nèi)參數(shù)的內(nèi)存地址與外界不同。因此:當(dāng)值類型的變量作為參數(shù)被傳入函數(shù)時,相當(dāng)于創(chuàng)建了新的常量并初始化為傳入的變量值,該參數(shù)的作用域及生命周期僅存在于函數(shù)體內(nèi)。

func swap(resCls: ResolutionClass) {
    print("During calling: \(Unmanaged.passUnretained(resCls).toOpaque())")
    let temp = resCls.height
    
    resCls.height = resCls.width
    resCls.width = temp
}

let iPhone5ResoClss = ResolutionClass()
iPhone5ResoClss.height = 1136
iPhone5ResoClss.width = 640
print(iPhone5ResoClss)
print("Before calling: \(Unmanaged.passUnretained(iPhone5ResoClss).toOpaque())")
swap(resCls: iPhone5ResoClss)
print(iPhone5ResoClss)
print("After calling: \(Unmanaged.passUnretained(iPhone5ResoClss).toOpaque())")

// ResolutionClass(height: 1136.0, width: 640.0)
// Before calling: 0x00006000000220e0
// During calling: 0x00006000000220e0
// ResolutionClass(height: 640.0, width: 1136.0)
// After calling: 0x00006000000220e0

小結(jié):在調(diào)用函數(shù)前后,外界變量值隨函數(shù)內(nèi)對參數(shù)的修改而發(fā)生變化,而且函數(shù)體內(nèi)參數(shù)的內(nèi)存地址與外界一致。因此:當(dāng)引用類型的變量作為參數(shù)被傳入函數(shù)時,相當(dāng)于創(chuàng)建了新的常量并初始化為傳入的變量引用,當(dāng)函數(shù)體內(nèi)操作參數(shù)指向的數(shù)據(jù),函數(shù)體外也受到了影響。

inout

inout 是 Swift 中的關(guān)鍵字,可以放置于參數(shù)類型前,冒號之后。使用 inout 之后,函數(shù)體內(nèi)部可以直接更改參數(shù)值,而且改變會保留。

func swap(resSct: inout ResolutionStruct) {
    withUnsafePointer(to: &resSct) { print("During calling: \($0)") }
    let temp = resSct.height
    resSct.height = resSct.width
    resSct.width = temp
}

var iPhone6ResoStruct = ResolutionStruct(height: 1334, width: 750)
print(iPhone6ResoStruct)
withUnsafePointer(to: &iPhone6ResoStruct) { print("Before calling: \($0)") }
swap(resSct: &iPhone6ResoStruct)
print(iPhone6ResoStruct)
withUnsafePointer(to: &iPhone6ResoStruct) { print("After calling: \($0)") }

// ResolutionStruct(height: 1334.0, width: 750.0)
// Before calling: 0x000000011ce62f50
// During calling: 0x000000011ce62f50
// ResolutionStruct(height: 750.0, width: 1334.0)
// After calling: 0x000000011ce62f50

小結(jié):值類型變量作為參數(shù)傳入函數(shù),外界和函數(shù)參數(shù)的內(nèi)存地址一致,函數(shù)內(nèi)對參數(shù)的更改得到了保留。

引用類型也可以使用 inout 參數(shù),但意義不大。

func swap(clss: inout ResolutionClass) {
    print("During calling: \(Unmanaged.passUnretained(clss).toOpaque())")
    let temp = clss.height
    clss.height = clss.width
    clss.width = temp
}

var iPhone7PlusResClss = ResolutionClass()
iPhone7PlusResClss.height = 1080
iPhone7PlusResClss.width = 1920
print(iPhone7PlusResClss)
print("Before calling: \(Unmanaged.passUnretained(iPhone7PlusResClss).toOpaque())")
swap(clss: &iPhone7PlusResClss)
print(iPhone7PlusResClss)
print("After calling: \(Unmanaged.passUnretained(iPhone7PlusResClss).toOpaque())")

// ResolutionClass(height: 1080.0, width: 1920.0)
// Before calling: 0x000060000003e580
// During calling: 0x000060000003e580
// ResolutionClass(height: 1920.0, width: 1080.0)
// After calling: 0x000060000003e580

需要注意的是:

  1. 使用 inout 關(guān)鍵字的函數(shù),在調(diào)用時需要在該參數(shù)前加上 & 符號
  2. inout 參數(shù)在傳入時必須為變量,不能為常量或字面量(literal)
  3. inout 參數(shù)不能有默認值,不能為可變參數(shù)
  4. inout 參數(shù)不等同于函數(shù)返回值,是一種使參數(shù)的作用域超出函數(shù)體的方式
  5. 多個 inout 參數(shù)不能同時傳入同一個變量,因為拷入拷出的順序不定,那么最終值也不能確定
struct Point {
    var x = 0.0
    var y = 0.0
}

struct Rectangle {
    var width = 0.0
    var height = 0.0
    var origin = Point()
    
    var center: Point {
        get {
            print("center GETTER call")
            return Point(x: origin.x + width / 2,
                         y: origin.y + height / 2)
        }
        
        set {
            print("center SETTER call")
            origin.x = newValue.x - width / 2
            origin.y = newValue.y - height / 2
        }
    }
    
    func reset(center: inout Point) {
        center.x = 0.0
        center.y = 0.0
    }
    
}

var rect = Rectangle(width: 100, height: 100, origin: Point(x: -100, y: -100))
print(rect.center)
rect.reset(center: &rect.center)
print(rect.center)

// center GETTER call
// Point(x: -50.0, y: -50.0)

// center GETTER call
// center SETTER call

// center GETTER call
// Point(x: 0.0, y: 0.0)

inout 參數(shù)的傳遞過程:

  1. 當(dāng)函數(shù)被調(diào)用時,參數(shù)值被拷貝
  2. 在函數(shù)體內(nèi),被拷貝的參數(shù)修改
  3. 函數(shù)返回時,被拷貝的參數(shù)值被賦值給原有的變量

官方稱這個行為為:copy-in copy-outcall by value result。我們可以使用 KVO 或計算屬性來跟蹤這一過程,這里以計算屬性為例。排除在調(diào)用函數(shù)之前與之后的 center GETTER call,從中可以發(fā)現(xiàn):參數(shù)值先被獲取到(setter 被調(diào)用),接著被設(shè)值(setter 被調(diào)用)。

根據(jù) inout 參數(shù)的傳遞過程,可以得知:inout 參數(shù)的本質(zhì)與引用類型的傳參并不是同一回事。inout 參數(shù)打破了其生命周期,是一個可變淺拷貝。在 Swift 3.0 中,也徹底摒除了在逃逸閉包(Escape Closure)中被捕獲。蘋果官方也有如下的說明:

As an optimization, when the argument is a value stored at a physical address in memory, the same memory location is used both inside and outside the function body. The optimized behavior is known as call by reference; it satisfies all of the requirements of the copy-in copy-out model while removing the overhead of copying. Write your code using the model given by copy-in copy-out, without depending on the call-by-reference optimization, so that it behaves correctly with or without the optimization.

作為一種優(yōu)化,當(dāng)參數(shù)是一個存儲于內(nèi)存中實際地址的值時,函數(shù)體內(nèi)外共用相同的一塊內(nèi)存地址。該優(yōu)化行為被稱作通過引用調(diào)用;其滿足 copy-in copy-out 模型的所有必需條件,同時消除了拷貝時的開銷。不依賴于通過引用調(diào)用的優(yōu)化,使用 copy-in copy-out 提供的模型來寫代碼,以便在進不進行優(yōu)化時(都能)正確運行。

嵌套類型

在實際使用中,其實值類型和引用類型并不是孤立的,有時值類型里會存在引用類型的變量,反之亦然。這里簡要介紹這四種嵌套類型。

值類型嵌套值類型

值類型嵌套值類型時,賦值時創(chuàng)建了新的變量,兩者是獨立的,嵌套的值類型變量也會創(chuàng)建新的變量,這兩者也是獨立的。

值類型嵌套值類型
struct Circle {
    var radius: Double
}

var circleA = Circle(radius: 5.0)
var circleB = circleA
circleA.radius = 10
print(circleA)
print(circleB)
withUnsafePointer(to: &circleA) { print("circleA: \($0)") }
withUnsafePointer(to: &circleB) { print("circleB: \($0)") }
withUnsafePointer(to: &circleA.radius) { print("circleA.radius: \($0)") }
withUnsafePointer(to: &circleB.radius) { print("circleB.radius: \($0)") }

// Circle(radius: 10.0)
// Circle(radius: 5.0)
// circleA: 0x000000011dc6dc90
// circleB: 0x000000011dc6dc98
// circleA.radius: 0x000000011dc6dc90
// circleB.radius: 0x000000011dc6dc98

值類型嵌套引用類型

值類型嵌套引用類型時,賦值時創(chuàng)建了新的變量,兩者是獨立的,但嵌套的引用類型指向的是同一塊內(nèi)存空間,當(dāng)改變值類型內(nèi)部嵌套的引用類型變量值時(除了重新初始化),其他對象的該屬性也會隨之改變。

值類型嵌套引用類型
class PointClass: CustomStringConvertible {
    var x: Double
    var y: Double
    
    var description: String {
        return "(\(x), \(y))"
    }
    
    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
}

struct Circle {
    var center: PointClass
}

var circleA = Circle(center: PointClass(x: 0.0, y: 0.0))
var circleB = circleA
circleA.center.x = 10.0
print(circleA)
print(circleB)
withUnsafePointer(to: &circleA) { print("circleA: \($0)") }
withUnsafePointer(to: &circleB) { print("circleB: \($0)") }
print("circleA.center: \(Unmanaged.passUnretained(circleA.center).toOpaque())")
print("circleB.center: \(Unmanaged.passUnretained(circleB.center).toOpaque())")

// Circle(center: (10.0, 0.0))
// Circle(center: (10.0, 0.0))
// circleA: 0x0000000118251fa0
// circleB: 0x0000000118251fa8
// circleA.center: 0x000060000003e100
// circleB.center: 0x000060000003e100

引用類型嵌套值類型

引用類型嵌套值類型時,賦值時創(chuàng)建了新的變量,但是新變量和源變量指向同一塊內(nèi)存,因此改變源變量的內(nèi)部值,會影響到其他變量的值。

引用類型嵌套值類型
class Circle: CustomStringConvertible {
    var radius: Double
    var description: String {
        return "Radius:\(radius)"
    }
    
    init(radius: Double) {
        self.radius = radius
    }
}

var circleA = Circle(radius: 0.0)
var circleB = circleA

circleA.radius = 5.0

print(circleA)
print(circleB)
print("circleA: \(Unmanaged.passUnretained(circleA).toOpaque())")
print("circleB: \(Unmanaged.passUnretained(circleB).toOpaque())")
withUnsafePointer(to: &circleA.radius) { print("circleA.radius: \($0)") }
withUnsafePointer(to: &circleB.radius) { print("circleB.radius: \($0)") }

// Radius:5.0
// Radius:5.0
// circleA: 0x000060000003bc80
// circleB: 0x000060000003bc80
// circleA.radius: 0x000060000003bc90
// circleB.radius: 0x000060000003bc90

引用類型嵌套引用類型

引用類型嵌套引用類型時,賦值時創(chuàng)建了新的變量,但是新變量和源變量指向同一塊內(nèi)存,內(nèi)部引用類型變量也指向同一塊內(nèi)存地址,改變引用類型嵌套的引用類型的值,也會影響到其他變量的值。

引用類型嵌套引用類型
class PointClass: CustomStringConvertible {
    var x: Double
    var y: Double
    
    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
    
    var description: String {
        return "(\(x), \(y))"
    }
}

class Circle: CustomStringConvertible {
    var center: PointClass
    var description: String {
        return "Center:\(center)"
    }
    
    init(center: PointClass) {
        self.center = center
    }
}

var circleA = Circle(center: PointClass(x: 0.0, y: 0.0))
let circleB = circleA

circleA.center.x = 5.0
print(circleA)
print(circleB)

print("circleA: \(Unmanaged.passUnretained(circleA).toOpaque())")
print("circleB: \(Unmanaged.passUnretained(circleB).toOpaque())")
print("circleA.center: \(Unmanaged.passUnretained(circleA.center).toOpaque())")
print("circleB.center: \(Unmanaged.passUnretained(circleB.center).toOpaque())")

// Center:(5.0, 0.0)
// Center:(5.0, 0.0)
// circleA: 0x0000608000025fa0
// circleB: 0x0000608000025fa0
// circleA.center: 0x0000608000025820
// circleB.center: 0x0000608000025820

總結(jié)

這篇文章是我在著手寫 Swift 中的 struct & class & enum 一文時抽離出來的一篇。主要還是圍繞了值類型中的 struct 和引用類型中的 class,在本文 stack & heap 一節(jié)中,只是簡單描述,因為一直對此部分內(nèi)容感到迷惑,也查閱很多資料,希望最近可以總結(jié)出來一篇小文,與大家分享。

When|值類型 Value Type|引用類型 Reference Type
-----|-----|-----|-----
1|== 有意義時|=== 有意義時
2|獨立|共享,可變
3|在多線程使用的數(shù)據(jù)|-

在本文的敘述中,可能有許多說法與您平時所用的術(shù)語略有差池,例如變量指向的內(nèi)存空間,其實也等價于變量指向的內(nèi)存地址。在行文過程中,查閱了很多國外的資料,也盡力將語言規(guī)范,以免產(chǎn)生歧義,如果有任何錯誤或建議,您都可以在評論中直接提出,我會研究學(xué)習(xí),虛心接受,并作出相應(yīng)整改。

參考資料

WWDC 2015 Building Better Apps with Value Types in Swift
Value and Reference Types
In-Out Parameters
In-Out Parameters
Reference vs Value Types in Swift: Part 1/2

延伸閱讀

Reference vs Value Types in Swift: Part 2/2
Swift 3 必看:foundation 中數(shù)據(jù)引用類型改為值類型
Mutability and Foundation Value Types

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374

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