《Effevtive c++ 》学习笔记

《Effective c++》 笔记

\color{blue}{1 让自己习惯c++}

\color{Fuchsia}{条款01:视c++为一个语言联邦}

  • c:说到底c++仍是以c为基础
  • object-Oriented C++。 这部分也是C with Classed所诉求的。
  • Template C++。这是c++的泛型编程部分,也是大多数程序员经验最少的部分。
  • STL。STL是个template程序库。

\color{Fuchsia}{条款02:尽量以const,enum,inline替换\#define}

\color{red}{请记住: }

  • 对于对于单纯常量,最好以const对象或enums替换#defines。
  • 对于形似函数的宏(macros),最好改用inline函数替换#define。

\color{Fuchsia}{条款03:尽可能使用const}

const成员函数

  • const成员函数不可以更改对象内任何non-static成员变量
  • 关键字<font color=blue>mutable</font>可以释放掉成员变量的bitwise constness约束;既此时const成员函数可以更改non-static成员变量。

代码:


    class CTextBlock
    {
       public:
              ...
            std::size_t length() const;
       private:
             char*pText;
             mutable std::size_t textLength;      //这些成员变量可能总是
             mutable bool lengthIsValid;          //会被改变,即使在
    };                                            //cosnt 成员函数内。
    std::size_t CTextBlock::length() const
    {
        if(!lengthIsValid){
        textLength=std::strlen(pText);            //现在,可以这样,
        lengthIsValid=true;                       //也可以这样。
        }
        return textLength;
    }

const和non-cosnt成员函数中避免重复

  • 当类中有函数重载,重载仅仅是const的区别那么会导致代码的大量的重复此时促使我们将常量性转移(casting away constness)

代码


    class TextBlock
    {
        public:
                ...
        const char& operator[](std::size_t postion) const//一如即往
        {
            ...
            ...
            ...
            return text[postion];
        }
            char& operatot[](std::size_t postion)//现在只调用const op[]
        {
            return 
                cosnt cast<char&>(
                    static_cast<const TextBlock&>(*this)[postion]
            );
        }
        ...
    };

\color{Plum}{代码解析:}

如你所见,这份代码有两个转型动作,而不是一个。我们打算让non-cosnt operator[]调用其const兄弟,但non-const operator[]内部若只是单纯的调用operator[],会递归调用自己。那会大概......悟......进行一百万次。为了避免无穷递归,我们必须明确指出调用的是const operator[],但c++缺乏直接的语法可以那么做。因此这里将*this从其原始类型TextBlock&转型为const TextBlock。是的,我么使用转型操作符为它加上const!所以这里共有两次转型:第一次用来为*this添加const(这使接下来调用operator[]时得以调用const版本),第二次是从const operator[]的返回值中移除const。

\color{red}{请记住:}

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可以被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体。
  • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const可避免代码重复。

\color{Fuchsia}{条款04:确定对象使用前已被初始化}

\color{red}{请记住:}

  • 为内置型对象进行手工初始化,因为c++不能保证初始化他们。
  • 构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

\color{green}{名词解释:}

  • local static对象:函数内的static对象。
  • non-local static对象:其他static对象。
  • c++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。

\color{blue}{2 构造/析构/赋值运算}

\color{Fuchsia}{条款05:了解C++默默编写并调用那些函数}

\color{red}{请记住:}

  • 编译器可以暗自为class创建default构造函数copy构造函数copy assignment操作符,以及析构函数

\color{red}{有些情况编译器拒绝生成以上函数:}

  1. 如果你打算在一个"内含reference成员(引用成员)"的class内支持赋值操作,你必须自己定义copy assignment操作符。
  2. 面都"内含const成员的classes,编译器的反应也一样,更改const成员是不合法的,所以编译器不知道如何在它自己生成的赋值函数内面对它们。
  3. 如果某个base classes 将copy assignemt操作符声明为private,编译器将拒绝为其derived classes生成一个copy assignment操作符。

\color{Fuchsia}{条款06:若不想使用编译器自动生成的函数,就应该明确拒绝}

\color{red}{请记住:}

  • 为驳回编译器(暗自)提供的机能,可以将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class 也是一种做法。

代码


    class Uncopyable
    {
     protected:
            Uncopyable() {};
            ~Uncopyable() {};
     private:
            Uncopyable(const Uncopyable&);
            Uncopyable& operator=(const Uncopyable&);
    };
    为阻止HomeForSale对象被拷贝,我们唯一需要做的就是继承Uncopyable:
    class HomeForSale:private Uncopyable{
     ...
    }


\color{Fuchsia}{条款07:为多态基类声明virtual析构函数}

\color{red}{请记住:}

  • polymorphic(带多态性质的)base classes 应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
  • Classes 的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不该声明vitrual析构函数。

\color{Fuchsia}{条款08:别让异常逃离析构函数}

\color{red}{请记住: }

  • 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。

代码


    //如果close抛出异常就结束程序。通常通过调用abort完成:
    DBConn::~DBConn()
    {
     try{db.close(); }
     catch(...){
        制作运转记录,记下对close的调用失败;
        std::abort();
     }
    }
    //吞下因调用close而发生的异常:
    DBConn::~DBConn()
    {
     try{db.close();}
     catch(...){
        制作运转记录,记下对close的调用失败;
     }
    }

  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

代码


    class DBConn{
    public:
    ...
    void close()
    {
     db.close();                             //供客户使用的新函数
     closed=true;
    }
    ~DBConn()
    {
     if(!closed){
        try
        {
            db.close();                      //关闭连接(如果客户不那么做的话)
        }
        catch(...){
            制作运转记录,记下对close的调用失败;//记录下来并结束程序
            ...                              //或吞下异常
        }
     }
    }
    }


\color{Fuchsia}{条款09:绝不在构造和析构过程中调用virtual函数}

\color{red}{请记住:}

  • 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)

\color{Plum}{解决方案:}

(P51)一种做法是在class Transaction 内将 logTransaction 函数改为non-virtual,然后要求derived class 构造函数传递必要信息给Transaction 构造函数,而后那个构造函数便可以安全的调用 non-virtual logTransaction。


\color{Fuchsia}{条款10:令operator= 返回一个reference\ to *this}

\color{red}{请记住: }

  • 令赋值(assignment)操作符返回一个reference to *this。

代码


    class Wideget{
     public:
     ...
     Widget& operator=(const Widget &ths)
     {
        ...
        return* this;
     }
     ...
    };


\color{Fuchsia}{条款11:operator= 中处理"自我赋值"}

\color{red}{请记住: }

  • 确保当对象自我赋值时operator= 有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址,精心周到的语句顺序,以及copy-and-swap。
  • 确定任何函数如操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

\color{Fuchsia}{条款12:复制对象时勿忘其每一个成分}

\color{red}{请记住: }

  • Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。
  • 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

\color{blue}{3 资源管理}

\color{Fuchsia}{条款13:以对象管理资源}

  • 重要思想:\color{DodgerBlue}{“以对象管理资源”}
  • 智能指针:auto_ptr和shared_ptr均为智能指针,可以自动管理new 分配的内存。但两者有区别(自行百度)。

\color{red}{请记住: }

  • 为防止资源泄露,请使用RAII(“以对象管理资源”的观念常被称为“资源取得时机便是初始化时机” Resource Acquisition Is Initialization; RAII)对象->(我将其称为资源管理类对象)。
  • 两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳的选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null。

\color{Fuchsia}{条款14:在资源管理类中小心*copying*行为}

\color{green}{提问:}当一个RAII对象被复制,会发生什么事?大多数时候会选择一下几种可能:

  • 禁止复制。许多时候允许RAII对象被复制并不合理。对一个像lock这样的class这是有可能的,因为很少能够合理拥有“同步化基础器物(synchronization primitives)”的复件(副本)。如果复制动作对RAII class并不合理,你便应该禁止之。条款6告诉你怎么做:将copying操作声明为private。对lock而言看起来是这样的:

代码


    class Lock:private Uncopyable{
    public:
        ...
    }

  • 对底层资源祭出“引用计数法”(reference-count)。 有时候我们希望保有资源,直到它的最后一个使用者(某对象)被销毁。这种情况下复制RAII对象时,应该将资源的“被引用次数”递增。tr1::shared_ptr便是如此。 通常只要内含一个tr1::shared_ptr成员变量,RAII classes便可实现出reference-counting copying 行为。如前述的Lock打算使用reference counting,它可以改变mutexPtr的类型,将它从Mutex*改为tr1::shared_ptr<Mutex>。然而很不幸tr1::shared_ptr的缺省行为是“当引用次数为0时删除其所指物”,那不是我们所要的行为。当我们用上一个Mutex,我们想要的释放动作是解除锁定而非删除。 幸运的是tr1::shared_ptr允许指定所谓的“删除器”(deleter),那是一个函数或函数对象(function object),当引用次数为0时便被调用(此机能并不存在于auto_ptr——它总是将其指针删除)。删除器对tr1::shared_ptr构造函数而言是可有可无的第二参数,所以代码看起来像这样:

代码


    class Lock{
    public:
     explicit Lock(Mutex* pm)               //以某个Mutex初始化                     
      : mutexPtr(pm,unlock)                 //并以unlock函数为删除器
     {
        lock(mutexPtr.get());               //条款15谈到"get"
     }
    private:
     std::tr1::shared_ptr<Mutex> mutexPtr;  //使用shared_ptr
    }                                           //替换raw pointer

\color{red}{注意:} 本例中Lock class不再声明析构函数。因为没有必要,条款5说过,class析构函数(无论是编译器生成的,还是用户自定的)会自动调用其non-static成员变量(本例为mutexPtr)的析构函数。

  • 复制底部资源。有时候只要你喜欢,可以针对一分资源拥有其任意数量的复件(副本)。而你需要“资源管理类”的唯一理由是,当你不再需要某个复件时确保它被释放。在此情况下复制资源管理对象,应该同时也复制其所包含的资源。也就是说,复制资源管理对象时,进行的是“深度拷贝”。
  • 转移底部资源的所有权。 某些罕见的场合下你可能希望确保永远只有一个RAII对象指向一个未加工资源(new resource),即使RAII对象被复制依然如此。此时资源的拥有权会从被复制物转移到目标物。一如条款13所述,这是auto_ptr奉行的复制意义。

\color{red}{请记住: }

  • 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
  • 普遍而常见的RAII class copying行为是:抑制copying,施行引用计数法(reference counting)。不过其他行为也都可能被实现。

\color{Fuchsia}{条款15:在资源管理类中提供对原始资源的访问}

\color{red}{请记住: }

  • APIs往往要求访问原始(raw resources),所以每一个RAII class应该提供一个"取得其所管理之资源"的方法。
  • 对原始资源的访问可能经由显示转换和隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。

\color{green}{名词解释:}

  • 显示转换:RAII class(资源管理类)应该提供一个类似auto_ptr类中的get()方法一样的函数,用于取得RAII class管理的资源。
  • 隐式转化:将RAII class隐式转换为其管理的资源的类型。代码实现需要运算符重载。

代码


    //给类添加隐式转换的代码
    class Font{
    public:
       ...
       operator double()const{return f;}//将类隐式的转换为double类型
       ...
    };
    eg.Font ft(); double ftt=ft;


\color{Fuchsia}{条款16:成对使用new和delete时要采取相同形式}

\color{red}{请记住: }

  • 如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。

\color{Fuchsia}{条款17:以独立语句将newed对象置入智能指针}

\color{red}{请记住: }

  • 以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。(详细原因见书p76)

代码


    std::tr1::shared_ptr<widget> pw(new Widget);    //在单独语句内以智能指针存储newed所得对象
    processWidget(pw,priority());                   //这个调用动作绝不至于造成泄露
    tr1::shared_ptr的构造函数是一个explicit构造函数


\color{blue}{4 设计与声明}

\color{Fuchsia}{条款18:让接口容易被正确使用,不易被误用}

  • 假设为一个用来表现日期的class设计构造函数:

    class Date {
    public:
     Date(int month,int day,int year);
     ...
    }

这段代码乍见之下同情达理,但它的客户很容易犯下至少两个错误:

  1. 他们也许会以错误的次序传参数:
    • Date d(30,3,1995); //哇哦!应该是"3,30"而不是"30,3"
  2. 他们可能传递一个无效的月份或天数:
    • Date d(2,30,1995); //哇哦!应该是"3,30"而不是"2,30"

解决方案:


    class Date{
    public:
     Date(const Month& m,const Day& d,const Year& y);
     ...
    }

令Day,Month,和Year成为成熟且经充分锻炼的classes并封装数据。

  • 一旦正确的类型就定位,限制其值有时候是通情达理的。例如一年只有12个有效月份,所以Month应该反映这一事实。办法之一是利用enum表现月份,但enmus不具备我们希望拥有的类型安全性,例如enums可被拿来当一个ints使用,比较安全的解法是预先定义所有有效的Months:

    class Month{
    public:
     static Month Jan() {return Month(1);}      //函数,返回有效月份。
     static Month Feb() {return Month(2);}      //稍后解释为什么。
     ...                                            //这些是函数而非对象
     static Month Dec() {return Month(12);}     
     ...                                            //其他成员函数
    private:
     explicit Month(int m);                     //阻止生成新的月份(构造函数声明为private)
     ...                                            //这是月份专属数据
    };

    Date d(Month::Mar(),Day(30),Year(1995));

如果“以函数替换对象,表现某个特定的月份”让你觉的诡异,或许是因为你忘记了non-locas static对象的初始化次序有可能出问题。建议阅读一下条款四。

  • 预防客户错误的另一个方法,限制类型内什么事可做,什么事不能做。常见的限制是加上const。例如条款3曾经说明为什么“以const修饰operator*的返回类型”可阻止客户因“用户自定义类型”而犯错:if(a*b=c)//哦,愿意其实是要做比较动作!

  • 条款13表明客户如何将createInvestment的返回值存储于一个智能指针如auto_ptr或tr1::shared_ptr内,因而将delete责任推给智能指针。但万一客户忘记使用智能指针怎么办?许多时候,较佳接口的设计原则是先发制人,就如factory函数返回一个智能指针:std::tr1::shared_ptr<Investment> crateIvestment();(shared_ptr可以自己指定“删除器”,它的构造函数的第二个参数)

  • tr1::shared_ptr有一个特别好的特性是:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误:所谓的"cross-DLL-problem"。这个问题发生于“对象在动态连接程序库(DLL)中被new创建,却在另一个DLL内被delete销毁”。在许多平台上,这一类“跨DLL之new/delete成对运用”会导致运行期错误。tr1::shared_ptr没有这个问题,因为它缺省的删除器是来自“tr1::shared_ptr诞生所在的那个DLL”的delete。

\color{red}{请记住:}

  • 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
  • “促进正确使用”的办法包括接口的一致性,以及于内置类型的行为兼容。
  • “阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
  • tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁(mutexes;见条款14)

\color{Fuchsia}{条款19:设计class犹如设计type}

设计class就是设计一个新的type(类型),设计class时应该考虑以下的问题:(P84)

  • 新的type的对象应该如何被创建和销毁?
  • 对象的初始化和对象的赋值有什么样的差别?
  • 新type的对象如果被passed by value(以值传递),意味着什么?
  • 什么是新type的“合法值”?
  • 你的新type需要配合某个继承图系(inheritance graph)吗?
  • 你的新type需要什么样的转换?
  • 什么样的操作符和函数对新type而言是合理的?
  • 什么样的标准函数应该驳回?
  • 谁该取用新type的成员?
  • 什么是新type的"未声明接口"(undeclared interface)?
  • 你的新type有多么一般化?
  • 你真的需要一个新type吗?

\color{red}{请记住:}

  • class的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过本条覆盖的所有讨论主题。

\color{Fuchsia}{条款20:宁以pass-by-reference-to-const替换pass-by-value}

  • 采用pass by reference to const的效率高。
  • 以by reference方式传递参数也可以避免slicing(对象切割)问题。当一个derived class对象以by value方式传递并被视为一个base class对象,base class的copy构造函数会被调用,而“造成此对象的行为像个derived class对象”的那些特化性质全被切割掉了,仅仅留下一个base class对象。这实在不怎么让人惊讶,因为正是base class构造函数建立了它。
  • pass by reference通常意味着真正传递的是指针。因此如果你有一个对象属于内置类型(例如int),pass by value往往比pass by reference的效率高。对内置类型而言,当你有机会选择采用pass-by-value或pass-by-reference-to-const时,选择pass-by-value并非没有道理。
  • “小型的用户自定义类型不必然成为pass-by-value优良候选人”的另一个理由是,作为一个用户自定义类型,其大小容易有所变化。
  • 一般而言,你可以合理假设“pass-by-value并不昂贵”的唯一对象就是内置类型和STL 的迭代器和函数对象。

\color{red}{请记住:}

  • 尽量以pass-by-reference-to-const 替换pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)。
  • 以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当。

\color{Fuchsia}{条款21:必须返回对象时,别妄想返回其reference}

\color{red}{请记住:}

  • 绝不要返回pointerreference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointerreference指向一个local static对象而有可能同时需要多个这样的对象。条款4已经为“在单线程环境中合理返回reference指向一个local static对象”提供了一份设计实例。

\color{Fuchsia}{条款22:将成员变量声明为private<}

\color{red}{请记住:}

  • 切记将成员变量声明为private。这可赋予客户访问数据的一致性,可细微划分访问控制,允诺约束条件获得保证,并提供class作者以充分的实现弹性。
  • protected并不比public更具有封装性。

\color{Fuchsia}{条款23:宁以non-member,non-friend,替换member函数}

  • 类的封装性:能后访问class内之private成分的函数数量。
  • 如果你要在memeber和一个non-memeber,non-friend函数之间做抉择,而且两者提供相同机能,那么,导致较大封装性的是non-memeber non-friend函数,因为它并不增加“能够访问class内之private成分”的函数数量。
  • 有两点值得注意:
    1. 这个论述只适用于non-member non-friend函数。friends函数对class private成员的访问权力和member函数相同,因此两者对封装的冲击力道也是相同的。从封装的角度看,这里选择的关键并不在memeber和non-member函数之间,而是在member和non-member non-friend函数之间。
    2. 封装性让函数“成为class的non-member”,并不意味着它“不可以是另一个class的memebr”。例如我们可以有一个工具类,可以让non-member non-friend函数成为这个工具类的static member函数。

\color{red}{请记住:}

  • 宁可拿non-member non-friend函数替换member函数。这样做可以增加封装性,包裹弹性和机能扩充性

\color{Fuchsia}{条款24:若所有参数皆需要类型转换,请为此采用non-member函数}

  • 不能够只因为函数不该成为memeber,就自动让它成为friend;

\color{red}{请记住:}

*如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。


\color{Fuchsia}{条款25:考虑写出一个不抛异常的swap函数}

  • 一般而言,重载function templates没有问题,但std是一个特殊的命名空间,其管理规则也比较特殊。客户可以全特化std内的templates,但不可以添加新的templates(或classes或functions或其他任何东西)到std里头。std的内容完全有c++标准委员会决定。
  • 如果swap的缺省实现码对你的class或class template提供可接受的效率,你不需要额外做任何事。任何尝试置换(swap)那种对象的人都会取得缺省版本,而那将有良好的运作。
  • 如果swap缺省实现版的效率不足(那几乎总是意味你的class或template使用了某种pimpl手法),试着做以下事情:
    1. 提供一个public swap成员函数,让它高效地置换你的类型的两个对象值。这个函数绝不该抛出异常。
    2. 在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数。
      3.如果你正在编写一个class(而非class template),为你的class特化std::swap,并令它调用你的swap成员函数。
    3. 最后,如果你调用swap,请确定包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸地调用swap。

\color{red}{请记住:}

  • 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
  • 如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes(而非templates),也请特化std::swap;
  • 调用swap时针对std::swap使用using声明式,然后调用swap并且不带任何"命名空间资格修饰"。
  • 为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

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

推荐阅读更多精彩内容