C++学习笔记1

前言

最近在学习C++,找到了浙大的翁凯老师的课,感觉翁老师讲的是真的好,真心感谢翁老师。这篇笔记是跟着他的课记下来的,我只写下了对我来说不太熟悉的内容或者说我认为比较重要的内容吧,零基础的可能不太适合看,毕竟这门课还是要求有一定C基础的。写笔记是为了我自己忘记的时候能回头补习一下,发表出来是希望最好能对有需要的朋友多少有点用处,有不明白的地方可以一起探讨,但强烈推荐去看翁凯老师的视频课程。写的东西不太系统,望谅解!

目录

  • 什么是对象?
  • 面向过程和面向对象的区别
  • C++中空间是什么时候开始分配的?
  • C++同一个类的不同对象之间的数据访问
  • C++中struct和class区别
  • C++中的initialize list(初始化列表)
  • 代码重用机制
  • 子类父类关系
  • 内联函数
  • const
  • 引用(references)
  • 向上造型(Upcasting)
  • 多态性(polymorphism)
  • 拷贝构造函数
  • 静态对象
  • 静态成员

什么是对象?

C++中“面向对象”其实翻译成对象导向是更合适的,但是这里不探讨它们之间谁更合适了。

C++中的对象指的是带有属性服务的一种对象。像下图中描述的那样:

面向过程和面向对象的区别

面向过程和面向对象的区别,请看下面的图片,下图中很好的解释了面向过程和面向对象的基本区别。C++的面向对象有点像对C中struct的一个扩充。在面向对象中,面对问题的时候,我们更侧重于思考这个问题中包含的things(东西),而不是对数据的操作流程(面向过程特点)。

C++中空间是什么时候开始分配的?

在C++中,空间是在进入大括号“{”时开始分配的,比如说有个函数需要被调用,而这个函数里面有各种对象和本地变量(或者叫数据),那这种情况下空间就是在进入到这个函数的大括号时才开始分配的。例如下面图片中的函数:

这里,当进入这个函数的“{”时,里面数据的空间就开始分配了。而当goto到jump1时,跳过了X x1;导致x1对象没有被构造(或者说没有调用构造函数),这时整个f函数执行完成后会调用X类的析构函数,由于x1没有被构造所以何谈析构,因此这时编译是会出现错误的。

C++同一个类的不同对象之间的数据访问

C++中同一个类的不同对象之间是可以互相直接访问私有成员变量的,即private是对类来说的,可以理解为这个类的所有的对象都是一家人,一家人之间可以互相分享数据。示例代码如下,这种情况下,b对象就可以访问a对象的私有成员变量的值。
C++中这种权限访问控制只是在编译时才去检查的,在运行时是不会去做访问检查的。因此,如果想干些坏事的话(如果有本事)可以在运行时为所欲为。

#include <iostream>

class A {
private:
    int i;
public:
    A() :i(0) { std::cout << "A()构造函数开始构造" << "且i的值为:" << i<<std::endl; }
    
    void set(int value) {
        i = value;
    }


    //可以获取不同对象的私有成员变量的值;
    void g(A* a) {
        std::cout <<"对象a中i的值为:"<< a->i << std::endl;
    }

    void print() {
        std::cout << "i的值为:" << i << std::endl;
    }

};


int main(void) {
    A a;
    A b;
    a.set(100);
    a.print();
    b.g(&a);

    system("pause");
    return 0;
}

C++中struct和class区别

C++中struct和class基本上是一样的,不同之处是class默认是private的,而struct默认的是public的,其他的都是一样的。

C++中的initialize list(初始化列表)

initialize list(初始化列表)如下图所示,第一个就是initialize list形式,第二个为传统形式。第一种形式即为真正的初始化,而第二种情况实则是先对name进行了default的初始化,然后再对name进行赋值。因此initialize list 动作会在构造函数之前就执行,也因此建议使用第一种方式。

我们来看一下下图,这个也是个initialize list 的初始化例子。如果m_saver和m_balance都在SavingsAccount构造函数中初始化的话,那么m_saver和m_balance都必须要有自己的默认构造函数(即无参的构造函数)。因为它们在这种情况下要进行像上面所说的,需要初始化,然后再赋值。

initialize list还有一个地方需要注意,就是它只能对非静态(static)成员变量进行初始化,对静态成员变量是不能进行初始化的。

代码重用机制

代码重用机制一般有两种:

  • composition(组合)
  • 继承

组合和继承都是软件代码重用的一种方式,组合是说拿已有的对象拼装出新的对象出来,所以组合的对象就是说成员变量是其他类的对象。继承是拿已有的类对其进行改造。

1. 继承的一些特性:
  • 连续继承情况下,构造函数的调用顺序
    如果有C 继承B,B继承A,那么当在main函数中创建C对象时,会首先调用A的构造函数,然后调用B的构造函数,最后调用C的构造函数。示例代码如下(代码里忘记写析构函数了,但不影响说明的问题):
#include <iostream>
using namespace std;

class A {
public:
    A():i(0) { cout << "A::A()开始构造!!" << endl; }

private:
    int i;
};

class B :public A {
public:
    B() { cout << "B::B()开始构造!!" << endl; }
};

class C :public B {
public:
    C() { cout << "C::C()开始构造!!" << endl; }
};


int main() {
    C c;

    system("pause");
    return 0;
}
  • 当A类中没有默认构造函数时,其子类应该采取的方法?
    其子类中应该在自己的构造函数的initialize list 里面初始化A类的带参数的构造函数,实例代码如下,这个实验也说明了,initialize list 的初始化要比构造函数早。这里要记得默认构造函数的调用时机。

#include <iostream>
using namespace std;

class A {
public:
    A(int ii):i(0) { cout << "A::A()开始构造!!" << "且ii的值为:"<< ii <<endl; }

private:
    int i;
};

class B :public A {
public:
    B(int iii):A(2){ cout << "B::B()开始构造!!" << "且iii的值为:" << iii << endl; }
};

class C :public B {
public:
    C():B(4){ cout << "C::C()开始构造!!" << endl; }
};


int main() {
    C c;

    system("pause");
    return 0;
}


子类父类关系

像下图中的代码所示,如果父类A中有print函数以及其overloading(重载)的函数print(int i){},且其子类B中也有print函数,那么A中的print函数以及reloading的print函数都可以理解为与B中的print函数是无关的,且C++会把A中的几个print函数隐藏起来,只有C++是这么干的(叫name hiding,名字隐藏),其它语言不会这么干。


内联函数

由于普通函数的调用会产生额外的开销(可以研究其编译成汇编代码的结果,从汇编代码中可以看到函数的执行步骤还是蛮多的,会有较多的堆栈操作即所谓的开销),因此如果一个函数代码量不是很大的话,可以考虑将其设为内联函数使用,内联函数用关键字inline写在函数最前面来表示。
下图是普通函数和inline函数编译成汇编的代码对比,可以看出inline比普通函数的操作步骤少了很多。

inline函数只是在编译时把该函数插入到调用它的地方,生成可执行文件后,文件里是没有该函数的。还有一点要记住,inline函数只写在.h头文件里(包括函数体)。为什么要这样子呢?我们来分析一下原因:如果我们在.h头文件里面声明了一个inline函数,然后在.cpp文件里面又用inline定义了一个该函数(带有函数体),那么当main函数调用这个函数时会出现连接错误(ld错误)。原因是main函数的文件里即使include了.h头文件,知道该函数是inline的,但是找不到该函数体(因为.cpp里面也是用inline声明的,编译器不进行编译),因此main函数在找不到该函数体的情况下就默认以call的形式去调用该函数,结果想要找到该函数去连接(linked)但是就是找不到,因此出现连接错误。
如果一个class,在.h头文件里面声明成员函数时把函数体也写进去时,那么该函数就默认的作为inline函数,就不需要.cpp文件了。


const

用const定义的仍然是一个变量而不是常数,对编译器来说变量是要在内存里面分配地址的。所以const修饰的也服从scope rules(作用域,如离开函数内存就释放了)。
用const定义的常量在定义时就要进行初始化。还有用const定义的常量可以为数组进行定义大小,原因就是在编译时,编译器已经知道了常量的大小,所以也就能给数组分配对应大小的空间了。然而,向下面这段代码在编译时是会出错的:

    int x;
    cin >> x;
    const int size = x;
    int gray[size]; //error!
指针指向的内存内容不可变 与 指针不可变

指针指向的内存内容不可变与指针不可变如下图所示,图片写的很清楚了。这里有一点需要说明的是,指针指向的内容不可变也只是说不能通过该指针去修改该内容而已,并不是说该内容就真的不能修改。

下面的图片我们只要看const是否在“ * ”号的前后就可以了,只要在“ * ”号前面就是对象是const的,在“*”号后面就是指针是const的。

下面的代码就说明了当cip所指向的对象是不可变时,是不能通过指针cip去修改i的内容的。但是i可以自己随便更改内容,然后*cip的内容也是跟着改变的。

    int i = 3;
    const int *cip;
    *cip = 19;  //error! 这一行是错误的
    cip = &i;
    i = 19;
    cout << *cip << endl;

    输出结果是:19

下图中,粉红色高亮的地方出错的原因是:ci本身是个const的,而ip没有用const修饰的,如果ip指向一个变量,那么就可以通过ip来更改该变量的值了,而ci本身又是不可以被修改的,如果可以的话就产生了安全问题,因此编译器是不会让这种情况发生的。然后我们看到可以将ci的地址赋给cip的,是因为cip也是const的,它所指向的内容也是不可以修改的,因此是安全的。

下图中的代码想说明的意思是:在第一行的代码中是用s1去指向"hello world"的,这里要注意,"hello world"是保存在代码段中的,这是一个有别于堆栈的特殊的存储区域,代码段中的内容是不可以被修改的,因此当用指针指向该区域内容时,一定要在前面加上const,否则会不安全,虽然不加const有时会编译通过,但是后面出现用该指针修改内容时便会出现错误。而第二行代码中s2是一个数组,它所指向的"hello world"是存放在堆栈中的(其实系统做了一份copy动作,将代码段的"hello world"拷贝到了堆栈,可以通过查看s1和s2的指针内容来证明两者不在一个存储区域)

const另一种有用的用法是当const修饰函数的参数时,比如下面的这个函数(没写函数体),表明f函数里面是不可以对x指向的内容进行修改的,只能用,不能修改,所以一定程度上对x的内容进行了保护。

void f(const int *x);

当const修饰一个对象时,说明该对象里面的数据是不可以被修改的,那对象里面的函数是否可以修改数据呢?其实也是不可以的,而且当一个对象被const修饰后,其所能调用的函数都要在函数后面加上一个const关键字,如下面的代码所示,意思是说只能调用带有const修饰的函数。代码中的两个f()函数其实是一对overload(重载)的关系,其实第一个f()函数是这样的:void f(A* this){},第二个f()函数是:void f(const A* this){},只不过这两个参数是隐藏的而已,到这里我们就应该明白怎么回事了,带const A* this参数的函数只能被const修饰的对象调用。

#include<iostream>
#include<stdio.h>
using namespace std;

class A {
private:
    int i;
public:
    A() :i(0) {}
    void f() { i++; cout << "f()" << endl; }

    void f() const { 
        cout << "f() const" << endl; 
    }
};


int main() {
    
    const A a;
    a.f();
    
    system("pause");
    return 0;
}

>> 输出结果:f() const

这里有一个地方需要注意下,就是如果a不是const的话而A类里面的成员变量i也没有被const修饰的话(即const int i),默认构造函数A()后面是可以不用initialize list进行初始化的,因为i是可修改的值。而如果a是const的,或者成员变量i是const的话,那么一定要在默认构造函数A()后面通过initialize list进行初始化,因为i 是const是不可以被修改的,一定要在开始进行初始化。

引用(references)

引用定义的时候必须要进行初始化,一定要有一个可以初始化的左值。只有两种情况下可以不用这种形式的初始化,即作为成员变量函数参数时,我们知道成员变量是对象创建时才进行初始化的,而函数参数是在函数调用时才进行初始化的。

char c;  //a character
char& r=c;  //a reference to a character

下面这段代码中,func函数的调用会出错,原因是i*3的结果是在一个匿名的临时变量存放的,匿名下func函数的参数是不会成功绑定到该变量的。(说明要想引用,一定要有名字)

void func(int &);
func(i*3);  //error or warning !

其实对引用的理解可以有两个层次,第一层可以简单的说引用就是另外一个变量的别名,但是如果想深入的理解清楚到底引用是怎么工作的话,那么实际上它就是一种指针操作,而且是const的指针(*后面的const,即指针不变),这是第二层面上的理解。当然,在使用时我们只把引用当做别名就可以了,否则会玩死自己的。

引用需要注意的一个地方

引用的使用有个知识点需要注意下,就是如果一个函数返回值为一个引用的话,那么(1)该函数返回值可以赋值给一个变量,不一定非得是引用;(2)该函数可以作为左值。
举个例子说明上面的内容,代码如下,当subscript(12)赋值给了一个普通变量value时,value是等于myarray[i]的值的,并不是它的引用。那最后一行34.5赋值给了subscript(3)其实是将34.5赋值给了myarray[3]。

#include<iostream>
using namespace std;
#include<stdio.h>
const int SIZE = 32;
double myarray[SIZE];
double& subscript(const int i) {
    return myarray[i];
}

main() {
    for (int i = 0; i < SIZE; i++) {
        myarray[i] = i * 0.5;
    }

    //函数返回值赋值给了一个不是引用的普通变量
    double value = subscript(12);

    //作为了左值
    subscript(3) = 34.5;

}


向上造型(Upcasting)

向上造型是指子类针对父类而言的,即拿一个子类对象当做父类对象来看待就叫Upcast。下图中三种关系都构成了Upcast:假如已知D类是B类的子类,那么有如下图的类型转换关系:(1)一个D类对象可以交给B类的变量;(2)一个D类型的指针可以交给B类型的指针;(3)一个D类型的引用可以交给一个B类型的引用。

举个例子,如下图,已知Manager是Employee的子类,Manager和Employee都有print函数(print函数是非virtual的,带virtual的是动态绑定,在下节的多态性里面讲)。那么当出现Upcast时,调用的print函数是父类的print函数,而不是子类的print函数(这也叫静态绑定)。

下面的代码也说明了上面的知识点。

#include<iostream>
#include<stdio.h>
using namespace std;

class A {
private:
    int i;
public:
    A() :i(10) {}
    void f() {  cout << "i=" <<i<< endl; }
    void set_i(int i) { this->i = i; }
};

class B :public A {
private:
    int j;
public:
    B() :j(20) {}
    void f() { 
        set_i(100);
        cout << "j=" << j << endl; 
    }
};

int main() {
    A a;
    B b;
    
    a.f();
    b.f();   //这步结束后,b对象里面的i值已经变成100了
    A c = b;
    c.f();


    system("pause");
    return 0;
}

>> 输出结果为:
i=10
j=20
i=100


多态性(polymorphism)

多态性建立在两个基础上的:

  • Upcast
  • Dynamic binding(动态绑定

Upcast我们都清楚了,那什么是Dynamic binding(动态绑定)呢?动态绑定是相对静态绑定来说的,静态绑定是指在编译阶段就知道要具体调用哪个函数,而动态绑定是在运行时才知道要调用哪个函数的。
那怎么实现动态绑定呢?实现动态绑定其实是使用virtual关键字实现的,也就是说在我们想要实现动态绑定的函数前面加上这个virtual关键字就可以了(不加virtual的函数就是静态绑定的),通常用virtual修饰的函数叫虚函数。虚函数一般要在基类就开始定义,然后其派生类中还要再定义此函数(最好也用virtual修饰)才能构成动态绑定。动态绑定是实现多态的基础,这里要注意实现多态(或动态绑定)必须要通过指针或者引用方式进行类型转换才可以
下面是实现多态的简单实例代码,其中Shape为基类。

#include<iostream>
#include<stdio.h>
using namespace std;

class XYPos {};

class Shape {
public:
    Shape() {};
    virtual ~Shape() {};
    virtual void render() {
        cout << "Shape's render!!" << endl;
    };
    void move();

protected:
    XYPos center;
};

class Ellipse :public Shape {
public:
    Ellipse() {};
    Ellipse(float maj, float minr) {};
    virtual void render() {
        cout << "Ellipse's render!!" << endl;
    };

protected:
    float major_axis, minor_axis;
};

class Circle :public Ellipse {
public:
    Circle() {};
    Circle(float radius) :Ellipse(radius, radius) {};
    virtual void render() {
        cout << "Circle's render!!" << endl;
    };
};

//用引用实现动态绑定
void render(Shape& p) {
    p.render();
}

/*//或者用指针实现动态绑定
void render(Shape* p) {
    p->render();
}
*/

int main() {
    
    Shape s;
    Ellipse e;
    Circle c;

    render(s);
    render(e);
    render(c);

    system("pause");
    return 0;
}

>> 输出结果:
Shape's render!!
Ellipse's render!!
Circle's render!!


拷贝构造函数

拷贝构造函数是一种比较特殊的构造函数,它在创建对象时,是使用之前已经创建好的同一类对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象
  • 把它作为参数传递给函数
  • 从函数返回这个对象

我们现在已经知道了,拷贝构造函数的调用会出现在以上的三个方面,那么我们下面就通过代码验证一下:

#include<iostream>
using namespace std;

int count = 0;

class A {
public:
    A() {
        ::count++;
        print("A constructed!!");
    };

    //拷贝构造函数
    A(const A& a) { 
        ::count++;
        print("A copy constructed");
    }

    void print(const char* s) {
        cout << s << endl;
        cout << ::count << endl;
    }

    A print(A a) {
        cout << "the second print func!!" << endl;
        return a;
    }

    ~A(){
        ::count--;
        print("A deconstructed!!");
    };
};


int main() {
    A a;
    A b = a.print(a);
    //A b = a;

    system("pause");
    return 0;
}

>> 输出结果:
A constructed!!
1
A copy constructed!!
2
the second print func!!
A copy constructed!!
3
A deconstructed!!
2
A deconstructed!!
1
A deconstructed!!
0

好现在来解释说明一下,当第一行代码A a执行时会调用构造函数(注意不是拷贝构造函数)。当第二行执行时,print(A a)函数里面的A a会执行第一次拷贝构造过程,因此这时count=2。然后执行cout << "the second print func!!" << endl;这句话。再然后执行 return a 时,这里还会进行一次拷贝构造过程,会构造一个匿名的对象,将a中的数据拷贝过来,因此这时的count=3,至此,拷贝构造共调用了两次,加上第一次A的构造函数,共三次构造,所以也共有三次析构。注意:这里有的人可能会觉得那A b = a.print(a);这句的A b不是应该还会调用一次拷贝构造吗?我刚开始对这个地方也有迷惑,但是我看了一篇博客,说的大概意思就是由于print函数中的return a构造了一个匿名的对象,系统直接将这个匿名对象给了b,不再做拷贝构造了,说这样做的原因是因为高效。如果是这样,那么就可以说通了。这个地方我暂时是这么理解的,如果你们如果有谁认为我讲的原理是错的,我希望你们可以尽快告诉我真正的原因。


静态对象

static关键字在C语言中有两个含义:(1)持久存储;(2)访问性受局限。而在C++中不仅具有此两种性质,而且还变得更加复杂,因为C++中有了静态的成员变量和成员函数。在C++中static有以下几种常用方式:

  • Static local variables     Persistent storage(持久存储)
  • Static member variables   Shared by all instances
  • Static member function  Shared by all instances,can only access static member variables

我们看下下图,这是在C中使用static的情形。File1中的g_global变量想要在File2中被使用,那么用extern关键字声明一下就可以了。而File1中的s_local变量由于用static修饰了,说明此变量只能在该文件中被访问(是有访问限制的),如果此时File2用extern关键字修饰s_local的话,编译时其实是可以通过的(编译器很好骗),但是在链接时就会出现问题了(因为一个说可以找到s_local,一个说只能自己使用,别人不能用),hidden函数也是如此。

下图是说当一个静态变量在一个函数中定义时,此静态变量只能在该函数内使用,说明访问是受限制的(我们要记住,用static修饰的东西都是全局的,只是这个全局的东西访问是受限制)。这里还有一点要说明一下,就是用static修饰的变量只能被初始化一次,像下面的 f 函数中的num_calls变量,当 f 函数第一次被调用时,此变量进行初始化。随后的多次访问 f 函数,都不会再执行初始化操作,而是直接对此变量进行++操作。

测试代码如下,用来验证上面那段话的正确性:

#include<iostream>
using namespace std;

void f() {
    static int num_calls = 0;
    cout << num_calls<<" ";
    num_calls++;
}

int main() {

    for (int i = 0; i < 10; i++) {
        f();
    }

    system("pause");
    return 0;
}
>> 输出结果:
0 1 2 3 4 5 6 7 8 9

static修饰的变量(1)是保存在全局数据区的(2)空间是在编译时就分配好了的。如果static修饰的是一个类对象,那么也是一样的。如果此对象是本地变量,那么析构是在函数结束后开始执行。如果是new出来的变量,析构是在delete时候发生的。如果是全局变量,析构是在程序结束的时候发生的。

下图中展示的是全局变量是在什么时候开始初始化结束的。global_x和global_x2变量的初始化是在程序刚开始运行时而main函数还没开始时进行的,这说明全局变量的构造函数是在main函数之前就开始了。所以说main函数不是第一个被调用的函数。那析构是在main结束后开始的或者程序结束后开始的。这里有个地方要注意的是,如果有多个.cpp文件,每个文件里面都有全局变量,那么这些全局变量的初始化顺序在每次运行时是不一样的。而且最好不同的.cpp文件之间的全局变量不要互相有依赖

这里介绍下全局(静态存储区):全局(静态)存储区分为 DATA 段和 BSS 段。DATA 段(全局初始化区)存放初始化的全局变量和静态变量;BSS 段(全局未初始化区)存放未初始化的全局变量和静态变量。


静态成员

在类中,是可以有静态成员的,那么静态成员的作用是怎样的呢?静态成员在该类的所有对象中都是存在的,而且在所有对象中的值是保持一致的。下面的代码实例就是为了说明这一点的,在这段代码中有一行代码是非常重要的,就是B A::b;这行代码,如果不加这行代码程序会出错,原因是在A中b是静态的,它只是一个全局的声明,并不是定义,我们知道要使用静态变量是要对其进行初始化的。而且,静态变量是可以直接通过类名进行访问,说明可以不用创建对象就访问该静态变量,因此如果某个对象想要访问自己的静态变量,那么在访问之前一定要对该静态变量进行定义(初始化),有些人可能觉得那在初始化列表(initialize list)中初始化是否可以,答案是不可以的,因为initialize list 只能对非静态成员进行初始化,不能对静态成员进行初始化

#include<iostream>
using namespace std;

class B {
public:
    B() :i(30) {};
    B(int ii) {
        i = ii;
    }
    void get_i() {
        cout << "i的值为:" << i << endl;
    }
    void set_i(int i) {
        this->i = i;
    }
private:
    int i;
};


class A {
public:
    A() {};
    static B b;
};

B A::b; //这个是定义,这行代码一定不能少的

int main() {
    
    A a;
    a.b.get_i();
    a.b.set_i(200);
    a.b.get_i();
    A b;
    b.b.get_i();

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