4.类和对象(构造深入)

数据成员指针

定义:
数据类型类名:: *指针名 = &类名::数据成员

解引用:
对象名.* 指针名
对象指针 ->*指针名

数据成员指针实际上是一个偏移量,区别于普通指针。

static静态成员变量不能使用数据成员指针

#include <iostream>
using namespace std;

struct A {
    A(double n1=0.0,int n2=0):num1(n1),num2(n2){}
    double num1;
    int num2;
};

int main(){
    int i = 10;
    int *pi = &i; //指针 指向普通变量

    A a(0.1, 1), b(1.1, 2);
    int *pa = &a.num2;
    cout << a.num2 << endl; //1

    int A::*p = &A::num2; //指针 指向成员变量
    cout << a.*p << b.*p << endl; // 1 2

    //指向数据成员的指针,是针对类的,是一个偏移量
    printf("%p\n", p); // 00000008 

    A* pA = new A(3.1, 3);
    cout << pA->*p << endl; //3

    auto p_double = &A::num1;
    cout << typeid(p_double).name() << endl; //double A::*

    delete pA;
    return 0;
}
成员函数指针

普通函数指针:返回值类型(*指针名)(参数列表)
注意:void( * p_fun)(int,int);和void * p_fun(int,int);的区别
前者是定义函数指针,后者是函数声明(指针函数)

成员函数指针的定义:
返回值类型(类名:: * 指针名)(参数列表)
解引用:
(对象名.* 指针名) (参数列表)
(对象指针 ->*指针名)(参数列表)
非静态成员函数指针与普通成员函数的区别是,隐含参数this指针
静态成员函数由于没有this指针,所以和普通函数指针是一样的,不能类名:: * 指针名

成员函数指针赋值时,不能省略&(取地址符)

#include <iostream>
using namespace std;

struct A {
    int add(int a, int b) { cout << "add()" << endl; return a + b; }
    static void show() { cout << "show()" << endl; }
};
void f1(int a, int b) { cout << "f1()" << endl; }
void f2(int a, int b) { cout << "f2()" << endl; }
typedef void(*P_FUN)(int, int);  //typedef 定义函数指针
using P_FUN1 = void(*)(int, int);//using 定义函数指针

typedef int(A::*P_ADD)(int, int);
using P_ADD1 = int(A::*)(int, int);
int main(){
    void(*p_fun)(int, int) = &f1; //普通函数指针,可省略&
    p_fun(2, 2); // f1()
    p_fun = &f2;
    p_fun(1, 2); // f2()

    P_FUN p1 = &f1;
    p1(1, 1);  // f1()
    p1 = &f2;
    p1(2, 2);  // f2()

    P_FUN1 p2 = &f1;
    p2(1, 1);  // f1()
    p2 = &f2;
    p2(2, 2);  // f2()

    //非静态成员函数指针,区别于普通函数,主要是由于隐含的this指针
    int(A::*p_add)(int, int) = &A::add; //非静态成员函数指针
    A a;
    cout << (a.*p_add)(0,2) << endl;     //.* 必须有对象,注意括号 运行结果add() 2
    A* pa = new A;
    cout << (pa->*p_add)(1, 2) << endl;  //->* //运行结果add() 3

    auto p_add1 = &A::add; 
    cout << (a.*p_add1)(2, 2) << endl;  //运行结果add() 4

    P_ADD p11 = &A::add;
    cout << (a.*p11)(3, 2) << endl; //运行结果add() 5
    P_ADD1 p22 = &A::add;
    cout << (pa->*p22)(2, 4) << endl; //运行结果add() 6

    delete pa;

    void(*p_show)() = &A::show; //静态成员函数指针 与普通函数指针类似
    p_show();
    return 0;
}
应用举例 :GAME类的方向移动函数封装

using Action = void(Game::*)();//定义别名 成员函数指针
相当于typedef void(Game::*Action)();

enum Direction { LEFT, RIGHT, UP, DOWN }; //枚举
默认从0开始,相当于LEFT=0,RIGHT=1,UP=2,DOWN=3
static Action menu[];
数组中存放的是 成员函数指针

三五法则

默认构造函数,拷贝构造函数,赋值运算符重载,析构函数,系统可自动合成。(自己没有定义的时候)
拷贝构造函数,赋值运算符重载,析构函数
一般情况下,要么都自己定义,要么都是系统合成。
有资源时,都自定义,没资源时,不必自己定义。
三个当中,只要有一个需要自定义,意味着其他两个也要自定义!
使用 =default; 显式要求编译器生成合成默认版本
使用 =delete; 定义为删除的函数。通知编译器不需要该函数。
private 也可以阻止拷贝,阻止赋值。
构造或析构函数定义为 private将无法在类外创建对象。
但是:构造public,析构private是可以用new创建对象的。

#include <iostream>
using namespace std;

class A {
public:
    A() = default;
    ~A() = default;
    A(const A&) = default;
    A& operator=(const A&) = default;
};
class B {
public:
    B() = default;
    B(const B&) = delete;
    B& operator=(const B&) = delete;
};
class C {
public:
    void destroy() { delete this; }
private:
    ~C() {};
};
int main() {
    A a1;
    A a2 = a1;
    B b1;
    //B b2 = b1; //错误,拷贝构造delete
    //C c1; //错误,析构函数是 private
    C* pc = new C;
    pc->destroy();
    return 0;
}

引用计数

思考:深拷贝一定就好吗?浪费内存空间(数据冗余存储)

深拷贝和赋值运算符重载(深)
#include <iostream>
#include <cstring>
using namespace std;
class myString {
public:
    myString(const char * pstr = NULL) {
        if (!pstr) {
            ps = new char[1];
            ps[0] = '\0';
        }
        else {
            ps = new char[strlen(pstr) + 1];
            strcpy(ps, pstr);
        }
    }
    myString(const myString &other):ps(new char[strlen(other.ps) + 1]) {
        strcpy(ps, other.ps);
    }
    myString &operator=(const myString &other) {
        if (this != &other) {
            delete[] ps;
            ps = new char[strlen(other.ps) + 1];
            strcpy(ps, other.ps);
        }
        return *this;
    }
    ~myString() {
        delete[] ps;
    }
private:
    char *ps;
};
int main() {
    myString s1 = "abc";
    myString s2 = s1;
    myString s3;
    s3 = s1;
    return 0;
}
浅拷贝
myString(const myString &other){
    ps = other.ps;//浅拷贝构造
} 
myString & operator = (const myString &other){
    ps = other.ps;
    return *this;//赋值运算符重载(浅)
}

思考:为了节约内存空间,使用浅拷贝,如何解决“重析构”“内存泄漏”的问题?
引用计数:增加一个计数器,记录当前指向同一块内存的次数,拷贝构造和赋值的时候: 计数+1,析构的时候: 计数-1 ,假如计数器==0,那么释放内存。
构造时:count = 1;
拷贝时:count++;
赋值时:count++;
析构时:

count--;
if(count == 0)
    delete[] ps;
class myString{
    char *ps;
    int count;//这样做不行
}
class myString{
    char *ps;
    static int count;
}//这样做也不行

正确做法:使用成员指针,计数器既有共通性,又有独立性。

class myString{
    char +ps;
    int *count;
}

构造时,为count开辟空间,并赋值 = 1,
在count == 0时,和ps同时释放内存。


构造函数

#include <iostream>
#include <cstring>
using namespace std;
class myString {
public:
    myString(const char * pstr = NULL) {
        if (!pstr) 
            ps = new char[1]{'\0'};
        else {
            ps = new char[strlen(pstr) + 1];
            strcpy(ps, pstr);
        }
        count = new int(1); //构造时开辟空间并置为1
    }
    myString(const myString &other) 
        :ps(other.ps), count(other.count) {
        (*count)++; //拷贝构造时 count + 1
    }
    myString &operator=(const myString &other) {//赋值运算符重载
        if (this != &other) {
            (*count)--; //本对象原来的计数器 -1
            if (*count == 0) {
                cout << "delete in = ." << endl;
                delete[] ps;
                delete count;
            }
            ps = other.ps;
            count = other.count;
            (*count)++;
        }
        return *this;
    }
    ~myString() { 
        (*count)--; //析构时 count - 1
        if (*count == 0) { //count为 0 释放内存
            delete[] ps;
            delete count;
            cout << "delete!" << endl;
        }
    }
    int get_count()const { return *count; }
private:
    char *ps;
    int *count;
};

int main() {
    myString s1("abc");
    myString s2 = s1; //拷贝构造
    cout << s1.get_count() << endl; //2
    myString s3("123");
    myString s4 = s3;
    cout << s3.get_count() << endl; //2
    s3 = s2;
    cout << s3.get_count() << endl; //3
    cout << s4.get_count() << endl; //1
    cout << "============" << endl;
    return 0;
}

写时拷贝

写的时候才会去更改,读的时候不会更改


void reverse() {
        if (*count > 1) { //除了自己外,还有人在用
            //重新复制一份以后,再处理
            (*count)--; //先把自己的计数去掉
            count = new int(1); //重新开辟新的计数空间
            char* tmp = ps; //保留原来的ps指针指向的位置
            ps = new char[strlen(tmp) + 1];
            strcpy(ps, tmp);
            //此时,本对象已经复制了一份拷贝,并新开了计数器
        }
        int len = strlen(ps) - 1;
        for (int i = 0; i < len / 2; i++) {
            char c = ps[i];
            ps[i] = ps[len - i];
            ps[len - i] = c;
        }
    }
SWAP 函数

函数重载,定义自己的SWAP的函数,交换指针

#include <iostream>
#include <cstring>
using namespace std;
class myString {
public:
    friend void swap(myString &a, myString &b);
    myString(const char * pstr = NULL) {
        if (!pstr) {
            ps = new char[1];
            ps[0] = '\0';
        }
        else {
            ps = new char[strlen(pstr) + 1];
            strcpy(ps, pstr);
        }
        cout << "Constructor: const char*" << endl;
    }
    myString(const myString &other) {
        ps = new char[strlen(other.ps) + 1];
        strcpy(ps, other.ps);
        cout << "Constructor: copy" << endl;
    }
    myString &operator=(const myString &other) {
        if (this != &other) {
            delete[] ps;
            ps = new char[strlen(other.ps) + 1];
            strcpy(ps, other.ps);
        }
        cout << "operator = " << endl;
        return *this;
    }
    ~myString() {
        delete[] ps;
        cout << "Delete[]" << endl;
    }
private:
    char *ps;
};

void std_swap(myString &a, myString &b) {
    myString tmp = a;
    a = b;
    b = tmp;
}
void swap(myString &a, myString &b) {
    std::swap(a.ps, b.ps);
}
int main() {
    myString s1 = "abc";
    myString s2 = "124";
    cout << "=============" << endl;
    std::swap(s1, s2); //std::swap函数
    cout << "=============" << endl;
    std_swap(s1, s2);  //模拟std::swap
    cout << "=============" << endl;
    //std::swap执行了深拷贝(拷贝构造和赋值运算符重载)
    swap(s1, s2); //自己定义的swap函数,没有深拷贝
    cout << "=============" << endl;
    return 0;
}

对象移动

拷贝构造生成临时量大量消耗资源,C++11对其进行了优化
移动语义是C++11的特性之一,利用移动语义可以实现对象的移动而非拷贝,在某些情况下,可以大幅度提升性能



移动概念:既然tmp马上要销毁,那么tmp指向的对内存资源是不是可以给别人?避免浪费

为了支持移动操作,引入“右值引用”。
右值引用:只能绑定到一个将要销毁的对象。因此:我们可以自由地将一个右值引用的资源“移动”到另外一个对象中。

左值持久,右值短暂。
由于右值引用只能绑定到临时对象:
1、所引用的对象将要被销毁;
2、该对象没有其他用户。
上面的2个特性意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。
左值、右值,左值引用、右值引用
左右值鉴别最简单的办法:左值可以用取地址操作符”&“获取地址,右值无法使用”&“。

int i  = 10;
int &r = i;// ok,标准的左值引用
int &&rr = i;//错误,不能讲一个右值引用绑定到一个左值上
// int &r2 = i *10;//错误,i * 10是个临时量,内置类型有const属性,所以必须是const int &r2 = i * 10;
const int &r2 = i * 10;
int &&r3 = i * 10;//ok,右值引用
int x = 0;//对象实例,有名,x是左值
int* p = &++X;//可以取地址,++x是左值
++x = 10;//前置++返回的是左值,可以赋值
//p = &x++;//后置++操作返回一个临时对象,不能取地址或赋值,是右值,编译错误

函数返回非引用类型时,是个临时量,所以是右值
注意:变量是左值,右值引用以后,相当于延长了临时量的生命周期,此时的临时量已经转换为左值了。
int && rr3 = i *10;
int &&rr4 = rr3; //错误, rr3已经是左值了!!
所以:引用(包括左值引用,右值引用)习惯上用在参数传递 和 函数返回值。

移动构造和移动赋值运算符重载
#include <iostream>
#include <cstring>
#include <string>
#include <utility>
using namespace std;

class myString {
public:
    myString(const char *str = nullptr); //构造
    myString(const myString &other);     //拷贝构造
    myString(myString &&other);          //移动构造
    myString &operator=(const myString &other);//赋值运算符重载
    myString &operator=(myString &&other); //移动赋值运算符重载
    ~myString() {
        if(ps)
            cout << ps << " --Destructor" << endl; 
        else
            cout << "ps is NULL" << " --Destructor" << endl;
        delete[] ps;
    }
    const char * c_str()const { return ps; }
private:
    char *ps;
};

myString::myString(const char *str) {
    if (str == nullptr) {
        ps = new char[1]{ 0 };
        cout << ps << " --Default constructor" << endl;
    }
    else {
        int length = strlen(str);
        ps = new char[length + 1];
        strcpy(ps, str);
        cout << ps << " --Str constructor" << endl;
    }
}

myString::myString(const myString &other) {//拷贝构造
    int length = strlen(other.ps);
    ps = new char[length + 1];
    strcpy(ps, other.ps);
    cout << ps << " --Copy constructor" << endl;
}
myString::myString(myString &&other):ps(other.ps) { //移动构造
    other.ps = nullptr;
    cout << ps << " --Move constructor" << endl;
}
myString &myString::operator=(const myString &other) { //赋值运算符重载
    if (this != &other) {
        delete[] ps;
        int length = strlen(other.ps);
        ps = new char[length + 1];
        strcpy(ps, other.ps);
    }
    cout << ps << " --Copy assignment" << endl;
    return *this;
}

myString &myString::operator=(myString &&other) {//移动赋值运算符重载
    if (this != &other) {
        delete[] ps;
        ps = other.ps;
        other.ps = nullptr;
    }
    cout << ps << "--Move assignment" << endl;
    return *this;
}

myString operator+(const myString &a, const myString &b) {
    int length = strlen(a.c_str()) + strlen(b.c_str());
    char * p = new char[length + 1];
    strcpy(p, a.c_str());
    strcat(p, b.c_str());
    myString tmp(p);
    delete[] p;
    return tmp;
}

myString fun() {
    myString tmp("allok");
    return tmp;
}
int main() {
    myString s1("abc");
    myString s2("123");
    myString s3 = s1; //拷贝构造
    cout << "1====================" << endl;
    myString s4(myString("ok"));
    cout << "2====================" << endl;
    myString s5 = s1 + s2; 
    // s1 + s2 是临时量(右值),调用移动构造给s4,马上析构
    cout << "3====================" << endl;
    myString s6 = fun();
    // fun()返回值 是临时量(右值),调用移动构造给s6,马上析构
    cout << "4====================" << endl;
    myString s7 = std::move(s6); //std::move函数,#include <utility>
    cout << (void*)s6.c_str() << endl; //s6中的资源ps=NULL了。
    cout << "5====================" << endl;
    return 0;
}

std::move 显式调用,强制移动。int &&r = std::move(r1);//将左值转换为右值
调用move以后,对r1只能赋值或者销毁,r1中的内容不再有意义。

vector动态增长

vector类似动态数组,所以在内存中是一段连续的内存。
观察sizeof(vector<myString>)sizeof(vector<int>)sizeof(vector<string>) 的大小,猜测vector的内存结构。

vector<myString> vs;
vs.size(); //此函数返回vector中的元素个数(已用空间数)
vs.capacity(); //此函数返回vector中的总空间个数
vs.reserve(n); //此函数预先分配一块指定大小为n个的内存空间。

添加元素时,如果vector空间大小不足,则会以原大小1.5倍重新分配一块较大新空间

// ConsoleApplication8.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
#pragma warning(disable:4996)
#define use_CRT_SECURE_NO_WARNINGS
#include <vector>
using namespace std;
int main() {
    vector<int> vs;
    cout << "size=" << vs.size() << " capacity=" << vs.capacity() << endl;
    for (int i = 0; i < 10; i++) {
        vs.push_back(i);
        cout << "size=" << vs.size() << " capacity=" << vs.capacity() << endl;
    }
    cout << "====================" << endl;
    vector<int> vs1;
    vs1.reserve(4);
    cout << "size=" << vs1.size() << " capacity=" << vs1.capacity() << endl;
    for (int i = 0; i < 4; i++) {
        vs1.push_back(i);
        cout << "size=" << vs1.size() << " capacity=" << vs1.capacity() << endl;
        cout << &vs1[0] << endl; //观察连续内存的起始地址
    }
    vs1.push_back(4);
    cout << "size=" << vs1.size() << " capacity=" << vs1.capacity() << endl;
    cout << &vs1[0] << endl; //观察连续内存的起始地址
    system("pause");
    return 0;
}

重新分配一块较大的新空间后,将原空间内容拷贝过来,在新空间的内容末尾添加元素,并释放原空间。也就是说vector的空间动态增加大小,并不是原空间之后的相邻地址增加新空间,因为vector的空间是线性连续分配的,不能保证原空间之后有供配置的空间


vector与移动
#include <iostream>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
using namespace std;

class myString {
public:
    myString(const char *str = nullptr); //构造
    myString(const myString &other);     //拷贝构造
    myString(myString &&other) noexcept; //移动构造
    myString &operator=(const myString &other);//赋值运算符重载
    myString &operator=(myString &&other) noexcept; //移动赋值运算符重载
    ~myString() {
        if (ps)
            cout << ps << " --Destructor" << endl;
        else
            cout << "ps is NULL" << " --Destructor" << endl;
        delete[] ps;
    }
    const char * c_str()const { return ps; }
private:
    char *ps;
};

myString::myString(const char *str) {
    if (str == nullptr) {
        ps = new char[1]{ 0 };
        cout << ps << " --Default constructor" << endl;
    }
    else {
        int length = strlen(str);
        ps = new char[length + 1];
        strcpy(ps, str);
        cout << ps << " --Str constructor" << endl;
    }
}

myString::myString(const myString &other) {//拷贝构造
    int length = strlen(other.ps);
    ps = new char[length + 1];
    strcpy(ps, other.ps);
    cout << ps << " --Copy constructor" << endl;
}
myString::myString(myString &&other) noexcept:ps(other.ps)  { //移动构造
    other.ps = nullptr; //要保证:移后源对象能正常析构
    cout << ps << " --Move constructor" << endl;
}
myString &myString::operator=(const myString &other) { //赋值运算符重载
    if (this != &other) {
        delete[] ps;
        int length = strlen(other.ps);
        ps = new char[length + 1];
        strcpy(ps, other.ps);
    }
    cout << ps << " --Copy assignment" << endl;
    return *this;
}

myString &myString::operator=(myString &&other) noexcept {//移动赋值运算符重载
    if (this != &other) {
        delete[] ps;
        ps = other.ps;
        other.ps = nullptr;
    }
    cout << ps << "--Move assignment" << endl;
    return *this;
}

myString operator+(const myString &a, const myString &b) {
    int length = strlen(a.c_str()) + strlen(b.c_str());
    char * p = new char[length + 1];
    strcpy(p, a.c_str());
    strcat(p, b.c_str());
    myString tmp(p);
    delete[] p;
    return tmp;
}

myString fun() {
    myString tmp("allok");
    return tmp;
}
//myString 实现了普通构造,拷贝构造,赋值运算符重载,析构
//         并且实现了 移动构造,移动赋值运算符重载

void fun1(vector<myString> &vs) {
    vs.push_back(myString("abc"));
    //do something...
}
vector<myString> fun2() {
    vector<myString> vs;
    vs.push_back(myString("123"));
    //do something...
    return vs;
}
int main() {
    //没有引入移动语义时,习惯这样用:
    //先准备好 vector, 然后用引用作为参数传递给函数
    vector<myString> vs1;
    fun1(vs1);
    cout << "==========================" << endl;
    //引入移动语义以后,这样写也非常ok
    vector<myString> vs2 = fun2();
    cout << "==========================" << endl;
    return 0;
}

自己编写的移动函数,最好加上noexcept。
vector保证:在调用push_back时发生异常,vector自身不会发生改变。
push_back可能会要求vector重新分配新内存,然后将元素对象从旧内存移动或者拷贝到新内存中。 假如使用拷贝,那么拷贝到一半的时候出现异常的话,由于旧内存的所有内容都还在,所以vector可以很容易恢复到原始状态。
假如使用移动,那么移动到一半的时候出现异常的话,旧空间的部分元素已经被改变了,而新空间中未构造的元素还不存在,此时vector将不能满足自身保持不变的要求。
为了避免这样的情况发生,除非vector知道元素类型的移动函数不会抛出异常,否则在重新分配内存的时候会使用拷贝构造而不是移动构造。
所以,通常将移动构造函数和移动赋值运算符重载标记为 noexcept。

class myString{
    myString(myString &&other) noexcept;//移动构造
  }
myString::myString(myString &&other) noexcept:ps(other.ps){
    other.ps = nullptr;
}

合成的移动函数:
1.自己没有定义拷贝构造、赋值运算符重载和析构函数;
2.类中所有非static数据成员都可移动时;同时满足上面两个条件,编译器会合成默认的移动函数。
移后源对象必须可析构;
移动右值,拷贝左值;
拷贝参数:const T& other 移动参数: T&& other
如果没有移动函数,右值也会被拷贝;
std::move
使用move可大幅提高性能,但是要小心使用 move操作,要绝对确认移后源对象没有其他用户。
为了效率,实现移动函数。比如自己写一个String类。

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

推荐阅读更多精彩内容