Swift5.x入门14--协议,Any,as,元类型,Error,泛型

protocol协议

  • 协议可以用来定义方法,下标,属性的声明;
  • 协议可以被类,结构体,枚举遵守,多个协议之间用逗号隔开;
  • 协议中定义方法时,不能有默认参数值;
  • 默认情况下,协议中定义的内容必须全部实现;
  • 协议中定义属性时必须使用var关键字;
  • 实现协议时的属性权限不小于协议中定义的属性权限;
    • 协议中定义get,set,可用var存储属性或者get,set计算属性去实现;
    • 协议中定义get,任何属性都可以实现;
import Foundation

protocol Drawable {
    func draw() -> Void
    var x: Int { get set }
    var y: Int { get }
    subscript(index: Int) -> Int { get set }
}
mutating
  • 只有将协议中的实例方法标记为mutating,才允许结构体,枚举的具体实现能修改自身内存,类在实现方法时不用加mutating,结构体和枚举才需要加mutating
协议中定义init初始化器
  • 协议中还可以定义初始化器;
  • final类实现时必须加上required
protocol Drawable1 {
    init(x: Int,y: Int)
}

class Point : Drawable1 {
    required init(x: Int, y: Int) {
        
    }
}

final class Size : Drawable1{
    init(x: Int, y: Int) {
        
    }
}
  • 如果从协议中实现的初始化器,刚好是重写了父类指定的初始化器,那么这个初始化必须同时加上requiredoverride关键字;
protocol Livable {
    init(age: Int)
}

class Person {
    init(age: Int) {
        
    }
}

class Student : Person,Livable {
    required override init(age: Int) {
        super.init(age: age)
    }
}
init,init?,init!
  • 协议中定义的init?,init!,可以用init,init?,init!去实现;
  • 协议中定义的init,可以用init,init!去实现;
protocol Livable {
    init()
    init?(age: Int)
    init!(no: Int)
}

class Person : Livable {
    required init() {} //可二选一
    required init!() {}
    
    required init?(age: Int) {} //可三选一
    required init!(age: Int) {}
    required init(age: Int) {}
    
    required init!(no: Int) {} //可三选一
    required init?(no: Int) {}
    required init(no: Int) {}
}
协议的继承
  • 一个协议可以继承其他协议;
protocol Runable {
    func run()
}


protocol Livable : Runable{
    func sleep()
}

class Person : Livable {
    func run() {
        
    }
    func sleep() {
        
    }
}
  • Livable协议继承Runable协议;
协议的组合
  • 协议组合一个类类型,最多只包含一个类;
protocol Livable {}
protocol Runable {}
class Person {}

//参数:接收Person或其子类的实例
func fn0(obj: Person) {}

//参数:接收遵守Livable协议的实例
func fn1(obj: Livable) {}

//参数:接收同时遵守Livable与Runable协议的实例
func fn2(obj: Livable & Runable) {}

//参数:接收同时遵守Livable与Runable协议的实例且是Person或其子类的实例
func fn3(obj: Person & Livable & Runable) {}

typealias RealPerson = Person & Livable & Runable
func fn4(obj: RealPerson) {}
CaseIterable协议
  • 让枚举遵守CaseIterable协议,可以实现遍历枚举值;
enum Season : CaseIterable {
    case spring,summer,autum,winter
}

//Season.allCases协议属性
let seasons = Season.allCases
print(seasons.count) //4

for season in seasons {
    print(season)
}
CustomStringConvertible协议
  • 遵守CustomStringConvertible协议,可以自定义打印字符串;
class Person : CustomStringConvertible {
    var age: Int
    var name: String
    init(age: Int,name: String) {
        self.age = age
        self.name = name
    }
    //实现协议方法
    var description: String {
        "age = \(age),name = \(name)"
    }
}

var person = Person(age: 31, name: "liyanyan")
print(person) //age = 31,name = liyanyan

Any 与 AnyObject

  • 在Swift中提供了两种特殊的类型:Any 与 AnyObject
  • Any :代表任意类型(枚举,结构体,类,函数类型)
  • AnyObject:代表任意类(class)类型;
  • 在协议后面加上: AnyObject,表示此协议只有类class才能遵守实现;

is,as,as?,as!

  • is:用来判断是否为某种类型;
  • as:用来做强制类型转换;
protocol Runnable {
    func run()
}

class Person  {
    
}

class Student : Person, Runnable{
    func run() {
        print("Student run")
    }
    func study() {
        print("Student study")
    }
}

//var stu: Any = 10
//print(stu is Int) //true
//stu = "li"
//print(stu is String) //true
//stu = Student()
//print(stu is Person) //true
//print(stu is Student)//true
//print(stu is Runnable) //true


var stu: Any = 10
//第一个问号:表示强制转换可能失败
//第二个问号:可选链
(stu as? Student)?.study() //no
stu = Student()
(stu as? Student)?.study() //yes
(stu as! Student).study()  //yes
(stu as? Student)?.run()   //yes

//数组 存放任意类型
var data = [Any]()
data.append(Int("123") as Any)
  • as!:确保能强转成功,由于是强制类型转换,如果转换失败会报 runtime 运行错误;
  • as?:不能确保能否强转成功,所以返回的是可选类型;

.self,.Type,AnyClass,type(of: T)

  • 我们知道单纯的数值5是Int类型,此时数值5是Int类型的一个值,Int类型是对具体数值5的一个抽象,可以这么说Int类型是抽象,数值5是具象,数值5占用多少内存空间,与数值5本身没有什么关系,这是由Int类型决定的,也就是说Int类型本身含有的信息数据决定了具体数值的内存分配,我们将描述Int类型的信息数据称之为元类型,简单而言元类型是描述类型的类型,存放着类型的相关信息,Int类型是描述具体整型数据的类型,存放着整型数据的信息;
  • 根据Int类型与数值5的关系,即Int类型是抽象,数值5是具象,数值5是Int类型的具体表现值,那么根据元类型的定义可知,元类型是类型的类型,那么元类型是抽象,在Swift中利用.Type进行表示,元类型的具体表现值是具体的类型,在Swift中使用.self进行表示;
  • 创建C语言工程,测试代码如下:
import Foundation

let intType: Int.Type = Int.self
let floatType: Float.Type = Float.self

print(intType) //Int
print(floatType) //Float
  • Int.Type是类型,是抽象,也就是所谓的元类型,Int.self是数值,是具象,具体的类型;
  • 元类型在有继承关系时,其数值不仅包含当前的类型,也包含当前类型的子类型,代码如下:
class YYAnimal {
    
}

class YYPerson: YYAnimal {
    
}

let type1: YYAnimal.Type = YYAnimal.self //success
let type2: YYAnimal.Type = YYPerson.self //success
let type3: YYPerson.Type = YYAnimal.self //报错
  • YYAnimal.Type是抽象,是元类型,其数值包括YYAnimal类型,也包括YYAnimal类型的子类型YYPerson类型;
  • 相反子类的元类型,其数值只能是子类类型以及子类的子类类型,不包括父类的类型;
class Person {
    
}
var person: Person = Person()
var pType: Person.Type = Person.self
  • 当断点停在var person: Person = Person()所在代码行,汇编代码如下:
Snip20210803_108.png
  • 第一个register read rax是在call 0x100003df0调用之后打印的,rax寄存器中存储的就是Person的类型信息;
  • 第二个register read rax是在callq 0x100003e70调用之后打印的,即实例化person对象,rax寄存器中存储的就是person实例对象的内存地址,即0x000000010071ead0
  • x/4gx 0x000000010071ead0查看person实例对象内存地址中的内容,前8个字节正是类型信息(Person.Type)的内存地址;
  • AnyObject:表示任意类类型;
  • AnyObject.Type:任意任意类类型的元类型,在Swift中用关键字AnyClass表示,即public typealias AnyClass = AnyObject.Type
  • type(of: 实例对象):返回的是实例对象所属类class的元类型,等价于实例对象所属类class.self,例如type(of: person) = Person.self

Self

  • Self:一般用作返回值类型,限定返回值跟方法调用者是同一类型;
protocol Runnable {
    func test() -> Self
}

class Person : Runnable{
    required init() {}
    func test() -> Self {
        //Person.init() 返回实例对象
        type(of: self).init()
    }
}
  • 如果Self用在类中,要求返回时调用的初始化器是required的;
  • Self代表当前类型;
class Person {
    var age: Int = 30
    static var cout: Int = 50
    func test() {
        print(self.age)
        print(Self.cout)
    }
}
  • print(self.age):self代表当前的实例对象;
  • print(Self.cout):Self代表Person类类型
class Person {
    static var age = 0
    static func run() {}
}

Person.age = 10
Person.run()

Person.self.age = 10
Person.self.run()

var p0 = Person()
var p1 = Person.self()
var p2 = Person.init()
var p3 = Person.self.init()

//var pType0 = Person
var pType1: Person.Type = Person.self

错误处理

  • 开发中的常见错误:
  • 语法错误,编译报错;
  • 逻辑错误;
  • 运行时错误,可能会导致闪退,一般也叫做异常;

自定义错误

  • 在Swift可以通过Error协议自定义运行时的错误信息;
  • 可能会抛出Error的函数,必须加上throws声明;
  • 函数内部通过throw抛出自定义Error;
  • 需要使用try调用可能会抛出Error的函数,若抛出Error必须要处理,如果不处理会导致程序闪退;
  • 可以使用do -- catch捕捉Error,进行Error处理;
enum MyError : Error {
    case illegaArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}

func divide(num1: Int,num2: Int) throws -> Int {
    if num2 == 0 {
        throw MyError.illegaArg("0不能作为除数")
    }
    return num1 / num2
}

func test() {
    print("1")
    do {
        print("2")
        print(try divide(num1: 200, num2: 0))//后面的代码停止执行
        print("3")
    } catch let MyError.illegaArg(msg) {
        print("参数异常:",msg)
    }catch let MyError.outOfBounds(size, index){
        print("下标越界:","size = \(size)","index = \(index)")
    }catch MyError.outOfMemory{
        print("内存溢出")
    }catch{
        print("其他错我")
    }
    print("4")
}

test()
  • 抛出Error后,try下一句直到作用域结束的代码都将停止执行,例如print(try divide(num1: 200, num2: 0))后面的print("3")不会执行;

Error的处理

  • 处理Error通常有两种方式:
  • 第一种:通过do -- catch捕捉Error,进行Error处理,上面已经演示过了;
  • 第二种:不捕捉Error,在当前函数添加throws声明,Error将自动抛给上层函数,如果最顶层函数,依旧没有捕捉处理Error,应用程序会闪退;
enum MyError : Error {
    case illegaArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}

func divide(num1: Int,num2: Int) throws -> Int {
    if num2 == 0 {
        throw MyError.illegaArg("0不能作为除数")
    }
    return num1 / num2
}

func test() throws{
    print("1")
    print(try divide(num1: 200, num2: 0))
    print("2")
}

try test() //会闪退
  • test(),没有捕捉处理Error,而是抛给了上层函数main;
  • main函数中没有处理Error,会报错;
enum MyError : Error {
    case illegaArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}

func divide(num1: Int,num2: Int) throws -> Int {
    if num2 == 0 {
        throw MyError.illegaArg("0不能作为除数")
    }
    return num1 / num2
}

func test0() throws{
    print("1")
    try test1()
    print("2")
}

func test1() throws{
    print("3")
    try test2()
    print("4")
}

func test2() throws{
    print("5")
    print(try divide(num1: 200, num2: 0))
    print("6")
}

try test0() //会闪退
  • test2中会出现Error,通过throws自动抛给上层test1,又由于throws,再次自动抛给上层test0,又由于throws,再次自动抛给上层main,main函数没有处理,则会闪退;

try?,try!

  • 可以使用try?,try!调用可能抛出Error的函数,这样就不用去处理Error;
enum MyError : Error {
    case illegaArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}

func divide(num1: Int,num2: Int) throws -> Int {
    if num2 == 0 {
        throw MyError.illegaArg("0不能作为除数")
    }
    return num1 / num2
}

func test2() {
    print("1")
    var result1 = try? divide(num1: 200, num2: 10) //Optional(2)
    var result2 = try? divide(num1: 200, num2: 0) //nil
    var reslut3 = try! divide(num1: 200, num2: 2) //Int 100
    print("2")
}
  • try? divide(num1: 200, num2: 0):会抛出错误,会返回nil,不会报错;
enum MyError : Error {
    case illegaArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}

func divide(num1: Int,num2: Int) throws -> Int {
    if num2 == 0 {
        throw MyError.illegaArg("0不能作为除数")
    }
    return num1 / num2
}

var a = try? divide(num1: 200, num2: 0)

//如果抛出异常,b的赋值操作不会执行 则默认为nil
//如果不抛出异常 b的赋值操作会执行
var b: Int?
do {
    b = try divide(num1: 200, num2: 0)
}catch{
    
}
  • a与b是等价的;

rethrows

  • rethrows:表明函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛;
enum MyError : Error {
    case illegaArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}

func divide(num1: Int,num2: Int) throws -> Int {
    if num2 == 0 {
        throw MyError.illegaArg("0不能作为除数")
    }
    return num1 / num2
}

func exec(_ fn: (Int,Int) throws -> Int,_ num1: Int,_ num2: Int) rethrows{
    print(try fn(num1,num2))
}

try exec(divide, 20, 0)
  • _ fn: (Int,Int) throws -> Int:闭包参数函数可能会出现Error,所以用throws进行声明,在其调用的时候用try
  • exec函数本身不会出现Error,错误是由调用闭包导致的,所以用rethrows进行声明,让外界调用者知道错误是谁抛出的;

defer

  • defer语句:用来定义以任何方式,离开代码块前必须要执行的代码;
  • defer语句将延迟至当前当前作用域结束之前执行;
enum MyError : Error {
    case illegaArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}

func divide(num1: Int,num2: Int) throws -> Int {
    if num2 == 0 {
        throw MyError.illegaArg("0不能作为除数")
    }
    return num1 / num2
}

func open(_ fileName: String) -> Int {
    print("open")
    return 0
}

func close(_ file: Int) -> Void {
    print("close")
}

func processFile(_ fileName: String) throws -> Void {
    let file = open(fileName)
    
    //use file
    //...
    
    try divide(num1: 100, num2: 0)
    
    close(file)
}

try processFile("test.txt")
  • processFile读取文件,当try divide(num1: 100, num2: 0)抛出Error,导致后面的代码执行不了,文件无法关闭,会导致内存泄漏;
  • 可使用defer语句进行改造,如下:
func processFile(_ fileName: String) throws -> Void {
    let file = open(fileName)
    //当前函数执行完成之前,会执行defer语句中的代码
    defer {
        close(file)
    }
    //use file
    //...
    
    try divide(num1: 100, num2: 0)
}
  • 再当前函数执行完成之前,会执行defer语句中的代码,保证文件能关闭;
  • defer语句的定义顺序与执行顺序相反;
func fn1() {
    print("fn1")
}

func fn2() {
    print("fn2")
}

func test() {
    defer {
        fn1()
    }
    
    defer {
        fn2()
    }
}

test() //fn2  fn1

断言(assert)

  • 很多编程语言都有断言机制,不符合条件的就抛出运行时错误,常用于调试阶段的条件判断;
  • 默认情况下,Swift断言只会在debug模式下生效,release模式下会忽略;
  • assert不能被捕捉,处理,直接闪退;
func divide(_ v1: Int,_ v2: Int) -> Int {
    assert(v2 != 0,"除数不能为0")
    return v1 / v2
}

fatalError

  • 如果遇到严重问题,希望结束程序运行,可以直接使用fatalError函数抛出错误,这是无法通过do--cacth捕捉的错误;
  • 若使用了fatalError函数,就不需要return 返回值了;
func test(_ num: Int) -> Int {
    if num >= 0 {
        return 1
    }
    fatalError("num不能小于0") //来到这里 会闪退
}
  • 在某些不得不实现,但又不希望别人调用的函数方法,可以考虑内部使用fatalError函数;
class Person {
    required init(){}
}

class Student : Person {
    var age: Int
    
    required init() {
        fatalError("不要调用这个初始化器")
    }
    init(age: Int) {
        self.age = age
    }
}

泛型

  • 泛型可以将类型参数化,提高代码的复用率,减少代码量;
函数参数使用泛型
func swapValues<T>(num1: inout T,num2: inout T) -> Void {
    let temp = num1
    num1 = num2
    num2 = temp
}

var n1 = 10
var n2 = 20
swapValues(num1: &n1, num2: &n2)
print(n1)
print(n2)

var d1 = 10.0
var d2 = 20.0
swapValues(num1: &d1, num2: &d2)
print(d1)
print(d2)

var fn1: (inout Int,inout Int) -> () = swapValues
fn1(&n1,&n2)


var fn2: (inout Double,inout Double) -> () = swapValues
fn2(&d1,&d2)
  • T是泛型,表明函数参数,可以传递任意类型;
类class使用泛型
class Stack<E> {
    var elements = [E]()
    func push(_ element: E) -> Void {
        elements.append(element)
    }
    func pop() -> E {
        elements.removeLast()
    }
    func top() -> E {
        elements.last!
    }
    func size() -> Int {
        elements.count
    }
}
 
//存储string的栈
var stringStack = Stack<String>()
//存储int的栈
var intStack = Stack<Int>()
//存储任意类型的栈
var anyStack = Stack<Any>()
  • 调用的时候,需要指定参数的类型;
函数返回值使用泛型
泛型函数的调用本质
func swapValues<T>(num1: inout T,num2: inout T) -> Void {
    let temp = num1
    num1 = num2
    num2 = temp
}

var n1 = 10
var n2 = 20
swapValues(num1: &n1, num2: &n2)

var d1 = 10.0
var d2 = 20.0
swapValues(num1: &d1, num2: &d2)
  • 汇编代码如下:
Snip20210803_109.png
Snip20210803_110.png
  • 可以看到传入不同的参数类型,调用的是同一个函数;

关联类型

  • 关联类型的作用:给协议中用到的类型 定义一个占位名称;
  • 协议中可以拥有多个关联类型;
protocol Stackable {
    associatedtype Element //关联类型
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}

class stringStack : Stackable {
    //给关联类型赋值真正要使用的类型
    //这行代码也可以不写,编译器也能正确确定要使用的类型
    typealias Element = String
    var elements = [String]()
    func push(_ element: String) {
        elements.append(element)
    }
    func pop() -> String {
        elements.removeLast()
    }
    
    func top() -> String {
        elements.last!
    }
    func size() -> Int {
        elements.count
    }
}
  • 协议中定义泛型,需要使用associatedtype关键字;
  • stringStack在实现Stackable协议时,必须要指定Stackable协议中定义的泛型类型,可使用typealias关键字,指定泛型的具体类型;

类型约束

protocol Runnable {
    
}

class Person {
    
}

func swapValues<T: Person & Runnable>(_ a: inout T,_ b: inout T) {
    (a,b) = (b,a)
}
  • swapValues函数参数是泛型类型,即可以是任意类型,但此任意类型需要满足Person & Runnable这个约束条件,即遵循Runnable协议的Person类型,才能作为入参,否则不能;
  • 泛型约束;
protocol Stackable {
    associatedtype Element : Equatable
}

class Stack<E : Equatable> : Stackable {
    typealias Element = E
}

func equal<S1: Stackable,S2: Stackable>(_ s1: S1, _s2: S2) -> Bool
    where S1.Element == S2.Element,S1.Element : Hashable
{
    return false
}
  • associatedtype Element : Equatable:Stackable协议中,定义泛型Element,且Element泛型遵循Equatable协议;
  • equal函数,其参数是泛型,且泛型遵循Stackable协议;
  • where S1.Element == S2.Element,S1.Element : Hashable是对关联类型的约束限制;
protocol Runnable { }

class Person : Runnable { }

class Car : Runnable { }

func get(_ type: Int) -> Runnable {
    if type == 0 {
        return Person()
    }
    return Car()
}

var r1 = get(0)
var r2 = get(1)
  • 上述代码没有问题,在编译期get方法的返回值是遵循Runnable协议的类型,是可以确定的,但具体是什么类型,必须要到运行行才能确定,只有传入参数,调用执行get方法,才能确定具体类型,再看一段代码:
protocol Runnable {
    associatedtype Speed
    var speed: Speed { get }
}

class Person : Runnable {
    var speed: Double { 0.0 }
}

class Car : Runnable {
    var speed: Int { 0 }
}

func get(_ type: Int) -> Runnable {
    if type == 0 {
        return Person()
    }
    return Car()
}

var r1 = get(0)
var r2 = get(1)
  • 上述代码会出现报错,原因在于协议中定义了关联类型(泛型);
  • 当在编译期时,只能确定get方法的返回值是遵循Runnable协议的对象,但是不能确定关联类型Speed的具体类型,所以会出现报错
  • 解决方案一:
protocol Runnable {
    associatedtype Speed
    var speed: Speed { get }
}

class Person : Runnable {
    var speed: Double { 0.0 }
}

class Car : Runnable {
    var speed: Int { 0 }
}

func get<T: Runnable>(_ type: Int) -> T {
    if type == 0 {
        return Person() as! T
    }
    return Car() as! T
}

var r1: Person = get(0)
var r2: Car = get(1)
  • r1类型确定,那么其speed的类型就随之确定;
  • 解决方案二:
protocol Runnable {
    associatedtype Speed
    var speed: Speed { get }
}

class Person : Runnable {
    var speed: Double { 0.0 }
}

class Car : Runnable {
    var speed: Int { 0 }
}

func get(_ type: Int) -> some Runnable {
    return Car()
}

var r1 = get(0)
var r2 = get(1)
  • get的返回值类型为some Runnable对于外界来说是不透明的类型,也就是说some Runnable,可以屏蔽内部的真实类型,对于外界只知道是遵循Runnable协议的类型;
  • some,限制只能返回一种类型,那么r1的类型确定,则speed的类型也就确定;

some关键字

  • some限制只能返回一种类型;
  • some用于限制返回值的类型上;
  • some还可以用于属性类型上;
  • some 的用法就是修饰在一个 protocol 前面,默认场景下 protocol 是没有具体类型信息的,但是用 some 修饰后,编译器会让 protocol 的实例类型对外透明;
import Foundation

func makeInt() -> Equatable {
    return 5
}

let a = makeInt()
let b = makeInt()

if a == b {
    print("equal")
}
  • 上述代码报错,原因在于Equatable协议作为函数返回值,在makeInt函数调用时,无法确定其返回值的具体类型;
  • 我们可以使用泛型约束对其进行改造,代码如下:
import Foundation

func makeInt<T: Equatable>() -> T {
    return 5 as! T
}

let a: Int = makeInt()
let b: Int = makeInt()

if a == b {
    print("equal")
}
  • 在使用泛型约束声明后,在代码调用的时候编译器可以通过类型推断出 返回值的具体类型是什么;
  • 使用泛型约束的语法来解决这个问题,比较冗余,可使用some关键字,在协议前面标记上 some 后,返回值的类型对编译器就变成透明的了,在这个值使用的时候编译器可以根据返回值进行类型推断得到具体类型,代码如下:
import Foundation

func makeInt() -> some Equatable {
    return 5 
}

let a = makeInt()
let b = makeInt()

if a == b {
    print("equal")
}
  • 注意:⚠️some限制只能返回一种类型,所以makeInt函数只能返回一种数据类型,如果返回多个数据类型,就会报错;

参考文章

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

推荐阅读更多精彩内容