C++ primer 第六章-函数

Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~

关于参数


参数赋值顺序

  • 我们只知道哪个实参对应哪个形参,the compiler is free to evaluate the arguments in whatever order it prefers.

参数类型转换

  • 和初始化的类型转换相似,比如:对于 char fact(int x) 来说,fact(3.14) 等价于 fact(3)

无形参时

  • char fact() 或者 char fact(void)

关于返回类型


  • 返回类型不能是 array 或 function

关于局部变量


作用域

  • They are “local” to that function and hide declarations of the same name made in an outer scope.

生存期

  • 定义在任何函数外的对象:exist throughout the program’s execution
  • 局部变量:depends on how it is defined

局部变量的分类

  • Automatic Objects:Objects that exist only while a block is executing
    • 栗子:形参(parameter)
    • 若未被初始化,Automatic Objects 会被 default initialize,因此,uninitialized local variables of built-in type have undefined values
  • Local static Objects:initialized before the first time execution passes through the object’s definition;生存期持续到程序终止
    • 若未被初始化,Local static Objects 会被 value initialize,因此,local statics of built-in type are initialized to zero
    • 栗子:会打印1~10
    size_t count_calls()
    {
      static size_t ctr = 0; // value will persist across calls
      return ++ctr;
    }
    
    int main()
    {
      for (size_t i = 0; i != 10; ++i)
        cout << count_calls() << endl;
      return 0;
    }
    

关于函数声明


基本性质

  • 声明之后才能使用函数
  • 函数只能定义一次,但可以声明多次
  • 函数声明中可以不写形参名字(但为了可读性通常会写)

栗子

void print(vector<int>::const_iterator beg,
vector<int>::const_iterator end);

声明与定义

  • 和变量一样,函数在头文件中声明,在source file中定义
  • The source file that defines a function should include the header that contains that function’s declaration. That way the compiler will verify that the definition and declaration are consistent.

关于Separate Compilation


概述

  • Separate compilation lets us split our programs into several files, each of which can be compiled independently.
  • 栗子:factMain.cc中的main函数调用了fact.cc中定义的fact函数,CC是编译器的名字;.o是object file,内含object code;The compiler lets us link object files together to form an executable
    # 以下为一起编译的情形
    $ CC factMain.cc fact.cc # generates factMain.exe or a.out
    $ CC factMain.cc fact.cc -o main # generates main or main.exe
    # 以下为独立编译的情形
    $ CC -c factMain.cc # generates factMain.o
    $ CC -c fact.cc # generates fact.o
    $ CC factMain.o fact.o # generates factMain.exe or a.out
    $ CC factMain.o fact.o -o main # generates main or main.exe
    

关于参数传递


基本性质

  • 若形参是一个引用,则它与实参(argument)绑定;否则,实参的值被复制并用于初始化形参

Passing Arguments by Value

  • 修改形参的值不会影响实参,哪怕它是指针;不过可以通过指针修改它指向的对象
  • 调用拷贝构造函数,把实参的值拷贝给形参
void reset(int *ip)
{
  *ip = 0; // changes the value of the object to which ip points
  ip = 0; // changes only the local copy of ip; the argument is unchanged
}

Passing Arguments by Reference

  • 直接调用构造函数,构造形参
  • Using References to Avoid Copies:避免复制带来的浪费,而且some class types (including the IO types) cannot be copied
  • Using Reference Parameters to Return Additional Information:通过引用隐式返回需要的值,比如:
string::size_type find_char(const string &s, char c,
string::size_type &occurs)
{
  auto ret = s.size(); // position of the first occurrence, if any
  occurs = 0; // set the occurrence count parameter
  for (decltype(ret) i = 0; i != s.size(); ++i) {
    if (s[i] == c) {
      if (ret == s.size())
        ret = i; // remember the first occurrence of c
      ++occurs; // increment the occurrence count
    }
  }
  return ret; // count is returned implicitly in occurs
}

关于用实参初始化形参的规则


top-level const

  • 传形参给实参时,是用实参初始化形参,因此top-level const会被忽略
  • 栗子
void fcn(const int i) { /* fcn can read but not write to i */ }
void fcn(int i) { /* . . . */ } // error: redefines fcn(int)

尽管这两个函数的形参看起来不一样,但由于top-level const会被忽略,所以we can pass exactly the same types to either version of fcn,所以第二个定义会报错

low-level const

  • 和普通变量的初始化一样,用实参初始化形参时low-level const不会被忽略,所以不能用low-level const初始化nonconst
int i = 0;
const int ci = i;
reset(&i); // calls the version of reset that has an int* parameter
reset(&ci); // error: can't initialize an int* from a pointer to a const int object

plain reference

  • a plain reference must be initialized from an object of the same type(可以隐式类型转换的也不行);plain reference不能引用字面量
string::size_type ctr = 0;
reset(i); // calls the version of reset that has an int& parameter
reset(ci); // error: can't bind a plain reference to the const object ci
reset(42); // error: can't bind a plain reference to a literal
reset(ctr); // error: types don't match; ctr has an unsigned type

关于用array做形参


基本性质

  • 直接用array名做实参,即passing a pointer to the array’s first element
  • 因为不能复制array,所以无法通过传值的方法,而是传指针
// 以下函数等价
// each function has a single parameter of type const int*
void print(const int*);
void print(const int[]); // shows the intent that the function takes an array
void print(const int[10]); // 这个10除了增加可读性外没有意义

PS:为什么最后一行的10没有意义?const int [10]的声明编译器自动处理为const int *,所以在函数的形参列表里指定数组的成员个数是没有意义的,传入的实参究竟是比10多还是比10少对于调用函数而言都是合法的

  • 也可以使用引用
// ok: parameter is a reference to an array; the dimension is part of the type
void print(int (&arr)[10])
{
  for (auto elem : arr)
    cout << elem << endl;
}
// 注意括号是必要的
f(int &arr[10]) // error: declares arr as an array of references

PS:为什么不能定义元素是引用的array?引用没有自身的地址,不占用内存空间,因此,声明引用数组没有办法分配内存空间,因为根本就没有空间可以分配给引用,所以不能声明和定义引用数组

多维array

  • 因为多维array是array的array,所以传的是一个指向array的指针
  • The size of the second (and any subsequent) dimension is part of the element type and must be specified
// matrix points to the first element in an array whose elements are arrays of ten ints
void print(int (*matrix)[10], int rowSize) { /* . . . */ }
// 等价于(第一维大小写不写无所谓,编译器会无视,自动处理为指向array的指针)
void print(int matrix[][10], int rowSize) { /* . . . */ }
// 注意第一行的括号是必要的
int *matrix[10]; // array of ten pointers

关于main函数的参数


int main(int argc, char *argv[]) { ... } // 等价于
int main(int argc, char **argv) { ... }
  • argv:an array of pointers to C-style character strings
  • argc:argv中的元素个数
  • argv中的第一个元素要么指向程序名字,要么指向空字符串,subsequent elements pass the arguments provided on the command line,最后一个元素是0,栗子:(命令行是 prog -d -o ofile data0)
argv[0] = "prog"; // or argv[0] might point to an empty string
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

关于形参个数可变的函数


initializer_list

  • 要求所有参数类型相同
  • 是一个array of values of the specified type
  • 定义在头文件initializer_list中
  • initializer_list中的元素都是const value
  • 操作
  • 栗子
void error_msg(ErrCode e, initializer_list<string> il)
{
  cout << e.msg() << ": ";
  for (const auto &elem : il)
    cout << elem << " " ;
  cout << endl;
}
// expected和actual都是string
if (expected != actual)
  error_msg(ErrCode(42), {"functionX", expected, actual});
else
  error_msg(ErrCode(0), {"functionX", "okay"});

variadic template

  • 参数类型可以不同
  • 在16章会讲

Ellipsis Parameters(省略号)

  • 最好只在need to interface to C functions的程序中使用
  • Ellipsis parameters should be used only for types that are common to both C and C+ + . In particular, objects of most class types are not copied properly when passed to an ellipsis parameter.
  • 省略号只能出现在参数list的最后,栗子:
void foo(parm_list, ...);
void foo(...);

No type checking is done for the arguments that correspond to the ellipsis parameter. In this first form, the comma following the parameter declarations is optional.

关于return


不一定非得含有return语句的

  • void型函数
  • main函数:若无return语句,the compiler implicitly inserts a return of 0

值传递:将返回值拷贝给临时变量,并返回临时变量

  • 函数returns a copy of variable(调用拷贝构造函数来初始化临时变量并返回)

引用传递:无拷贝,返回对象本身

  • 函数返回值为引用时,返回的是左值;返回值为其他类型时,返回的是右值
char &get_val(string &str, string::size_type ix)
{
  return str[ix]; // get_val assumes the given index is valid
}

int main()
{
  string s("a value");
  cout << s << endl; // prints a value
  get_val(s, 0) = 'A'; // changes s[0] to A
  cout << s << endl; // prints A value
  return 0;
}

不要返回局部变量的引用或指针

const string &manip()
{
  string ret;
  // transform ret in some way
  if (!ret.empty())
    return ret; // WRONG: returning a reference to a local object!
  else
    return "Empty"; // WRONG: "Empty" is a local temporary string
}

返回{...}

  • In a function that returns a built-in type, a braced list may contain at most one value, and that value must not require a narrowing conversion

main函数中的return

  • A zero return indicates success; most other values indicate failure. A nonzero value has a machine-dependent meaning.
  • 头文件cstdlib中定义了如下两个preprocessor variables:
int main()
{
  if (some_failure)
    return EXIT_FAILURE; // defined in cstdlib
  else
    return EXIT_SUCCESS; // defined in cstdlib
}

PS:preprocessor variables前不能加std::这种,也不能使用using声明

返回array

  • 因为无法复制array,所以函数不能直接返回array,但可以返回a pointer or a reference to an array
  • 函数声明
// 法一:使用别名
typedef int arrT[10]; // arrT is a synonym for the type array of ten ints
arrT* func(int i); // func returns a pointer to an array of five ints
// 法二:不使用别名
int (*func(int i))[10]; // 理解:(*func(int))表示可以dereference func的返回结果,[10]表示返回结果是大小为10的array,int表示返回的array元素为int
// 法三:trailing return type
auto func(int i) -> int(*)[10];
// 法四:decltype
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
// returns a pointer to an array of five int elements
decltype(odd) *arrPtr(int i)
{
  return (i % 2) ? &odd : &even; // returns a pointer to the array
}

什么时候会产生临时变量


值传递

  • 函数返回使用值传递时:将返回值拷贝给临时变量,并返回临时变量

引用传递

  • 形参是plain reference时:当实参和形参的类型不匹配会产生临时变量
  • 形参是const reference时:当实参和形参的类型匹配,但实参不是左值时,会产生临时变量;当实参和形参的类型不匹配,但是可以转换为正确的类型时,会产生临时变量

类型转换

  • 强制类型转换时:会产生临时变量

重载函数


基本性质

  • 重载函数:Functions that have the same name but different parameter lists and that appear in the same scope
  • must differ in the number or the type(s) of their parameters
  • 有无top-level const不影响type difference
// 以下两个函数属于重复declare
Record lookup(Phone);
Record lookup(const Phone); // redeclares Record lookup(Phone)
// 以下两个函数属于重载,对于nonconst对象两个函数都可以使用,不过编译器会prefer用第二个
Record lookup(Account&); // function that takes a reference to Account
Record lookup(const Account&); // new function that takes a const reference
  • 类中的const与nonconst成员函数可以重载,因为this指针类型不同
class A
{
public:
    void foo(void)
    {
        cout << "非常函数" << endl;
    }
    void foo(void)const 
    {
        cout << "常函数" << endl;
    }
};

重载和作用域

  • if we declare a name in an inner scope, that name hides uses of that name declared in an outer scope,而不是重载outer scope的函数,因为local definition hides outer definition
string read();
void print(const string &);
void print(double); // overloads the print function
void fooBar(int ival)
{
  bool read = false; // new scope: hides the outer declaration of read
  string s = read(); // error: read is a bool variable, not a function
  // bad practice: usually it's a bad idea to declare functions at local scope
   void print(int); // new scope: hides previous instances of print
  print("Value: "); // error: print(const string &) is hidden
  print(ival); // ok: print(int) is visible
  print(3.14); // ok: calls print(int); print(double) is hidden
}

重载函数的匹配

  • 第一步:找candidate functions:函数名相同 + visible declaration
  • 第二步:找viable functions:参数个数相同 + 参数类型匹配(convertible也可以)
  • 第三步:找best match:
    • The match for each argument is no worse than the match required by any other viable function
    • There is at least one argument for which the match is better than the match provided by any other viable function
  • 若没有best match,则编译器会报错:the function call is ambiguous
  • 附:参数类型转换排序(用于决定谁是best match)
    1. exact match:① argument and parameter types are identical;② argument is converted from an array or function type to the corresponding pointer type;③ top-level const is added to or discarded from the argument
    2. match through a const conversion
    Record lookup(const Account&);
    Account b;
    lookup(b);
    
    1. match through a promotion
    2. match through an arithmetic or pointer conversion
    3. match through a class-type conversion(第14章会讲)

参数类型转换

  • small integral types always promote to int or to a larger integral type
  • all the arithmetic conversions are treated as equivalent to each other
void manip(long);
void manip(float);
manip(3.14); // error: ambiguous call

默认参数


默认参数位序

  • if a parameter has a default argument, all the parameters that follow it must also have default arguments
  • 排序原则:those least likely to use a default value appear first and those most likely to use a default appear last

调用含默认参数的函数

typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
string window;
window = screen(); // equivalent to screen(24,80,' ')
window = screen(66);// equivalent to screen(66,80,' ')
window = screen(66, 256); // screen(66,256,' ')
window = screen(66, 256, '#'); // screen(66,256,'#')
window = screen(, , '?'); // error: can omit only trailing arguments

含默认参数的函数的声明

  • any subsequent declaration can add a default only for a parameter that has not previously had a default specified
  • defaults can be specified only if all parameters to the right already have defaults
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*'); // error: redeclaration
string screen(sz = 24, sz = 80, char); // ok: adds default arguments

默认参数与局部变量

sz wd = 80;
char def = ' ';
sz ht();
string screen(sz = ht(), sz = wd, char = def);
string window = screen(); // calls screen(ht(), 80, ' ')

void f2()
{
  def = '*'; // changes the value of a default argument
  sz wd = 100; // hides the outer definition of wd but does not change the default
  window = screen(); // calls screen(ht(), 80, '*')
}
  • 局部变量wd hides the outer definition of wd,但这个局部变量与传给screen的默认参数无关

inline函数


含义

  • A function specified as inline (usually) is expanded “ in line” at each call.

好处

  • 函数调用的代价大:Registers are saved before the call and restored after the return; arguments may be copied; and the program branches to a new location
  • inline函数可以避免上述代价

栗子

  • shorterString是一个返回较短string的函数,若它是一个inline函数,则:
inline const string &
shorterString(const string &s1, const string &s2)
{
  return s1.size() <= s2.size() ? s1 : s2;
}

cout << shorterString(s1, s2) << endl;
// would be expanded during compilation into
cout << (s1.size() < s2.size() ? s1 : s2) << endl;

注意

  • inline specification is only a request to the compiler, the compiler may choose to ignore this request
  • many compilers will not inline a recursive function
  • inline函数可以多次定义(毕竟the compiler needs the definition, not just the declaration, in order to expand the code),但每次定义必须是一样的,因此为了避免麻烦,inline函数通常定义在头文件中
  • functions defined in the class are implicitly inline
  • 关键字inline必须与函数定义放在一起才能使函数成为内联函数,仅仅将inline放在函数声明前面不起任何作用
  • 与宏定义的区别:宏定义是预编译时替换的,没有类型检查;内联函数是在编译时替换的,有类型检查

constexpr函数


定义

  • a function that can be used in a constant expression
  • return类型和参数类型必须是字面量
  • 函数体 must contain exactly one return statement

性质

  • constexpr函数是隐式inline的:the compiler will replace a call to a constexpr function with its resulting value
  • 函数体也可包含其他语句,只要它们generate no actions at run time,比如null statements,type aliases和using声明
  • constexpr函数的返回类型可以不是constant expression
// scale(arg) is a constant expression if arg is a constant expression
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }

int arr[scale(2)]; // ok: scale(2) is a constant expression
int i = 2; // i is not a constant expression
int a2[scale(i)]; // error: scale(i) is not a constant expression
  • constexpr函数可以多次定义(毕竟the compiler needs the definition, not just the declaration, in order to expand the code),但每次定义必须是一样的,因此为了避免麻烦,constexpr函数通常定义在头文件中

debug帮手


assert

  • 是一个preprocessor macro
  • 用法:若expr==false,assert writes a message and terminates the program;若expr==true,assert does nothing
assert(expr);
  • 定义在头文件cassert中
  • preprocessor names are managed by the preprocessor not the compiler,所以可以直接使用preprocessor names,不需要用std::或using等

NDEBUG

  • 是一个preprocessor variable
  • If NDEBUG is defined, assert does nothing. By default, NDEBUG is not defined, so, by default, assert performs a run-time check.
  • 定义NDEBUG的方法
// 法一
$ CC -D NDEBUG main.C
// 法二
#define NDEBUG
  • 与_ _func_ _配合使用
void print(const int ia[], size_t size)
{
  #ifndef NDEBUG
  cerr << _ _func_ _ << ": array size is " << size << endl;
  #endif
  // ...
}

PS: _ _func_ _可以打印正在debug的函数的名字;The compiler defines _ _func_ _ in every function. It is a local static array of const char that holds the name of the function.

  • 与其他preprocessor names配合使用
if (word.size() < threshold)
cerr << "Error: " << _ _FILE_ _
  << " : in function " << _ _func_ _
  << " at line " << _ _LINE_ _ << endl
  << " Compiled on " << _ _DATE_ _
  << " at " << _ _TIME_ _ << endl
  << " Word read was \"" << word
  << "\": Length too short" << endl;

指向函数的指针


基本性质

  • 函数的类型取决于返回类型和参数类型,比如 bool(const string&, const string&) 是一个函数类型
  • 栗子
bool lengthCompare(const string &, const string &);
// pf points to a function returning bool that takes two const string references
bool (*pf)(const string &, const string &); // uninitialized

初始化

pf = lengthCompare; // pf now points to the function named lengthCompare
pf = &lengthCompare; // equivalent assignment: address-of operator is optional

PS:可以用nullptr或者zero-valued integer constant expression初始化函数指针,表示它不指向任何函数

使用指针调用函数

bool b1 = pf("hello", "goodbye"); // calls lengthCompare
bool b2 = (*pf)("hello", "goodbye"); // equivalent call
bool b3 = lengthCompare("hello", "goodbye"); // equivalent call

函数指针做函数的参数

  • 和array一样,函数不能直接做函数的参数,但参数可以是指向函数的指针
// third parameter is a function type and is automatically treated as a pointer to function
void useBigger(const string &s1, const string &s2,
bool pf(const string &, const string &));
// equivalent declaration: explicitly define the parameter as a pointer to function
void useBigger(const string &s1, const string &s2,
bool (*pf)(const string &, const string &));

// automatically converts the function lengthCompare to a pointer to function
useBigger(s1, s2, lengthCompare);

函数指针做返回值

  • 和array一样,函数不能直接做函数的返回值,但返回值可以是指向函数的指针
PF f1(int); // ok: PF is a pointer to function; f1 returns a pointer to function
F f1(int); // error: F is a function type; f1 can't return a function
F *f1(int); // ok: explicitly specify that the return type is a pointer to function

函数及函数指针的type aliases

// 以下两种定义等价,Func是函数类型
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func; 
// 以下两种定义等价,FuncP是函数指针类型
typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP;

using F = int(int*, int); // F is a function type, not a pointer
using PF = int(*)(int*, int); // PF is a pointer type
  • 最好使用别名,不然定义会非常复杂
    int (*f1(int))(int*, int);
    • 上面这个定义的含义:f1有参数list,所以f1是一个函数;f1前有个*,所以f1返回指针;The type of that pointer itself has a parameter list, so the pointer points to a function;That function returns an int.
    • 等价于 auto f1(int) -> int (*)(int*, int);

关于临时变量


临时变量的特点

  • invisible,在程序代码中没有显式出现
  • non-named

产生临时变量的情形

  • 值传递时
  • 常引用传递且需要进行类型转换时
double d; 
const int &ref=d; 
// 其实相当于
int tmp=d; 
const int & ref=tmp;

PS:c++不允许为非常引用产生临时变量,因为非常引用意味着可能会修改被引用的变量,若产生临时变量,则修改的是临时变量,没有意义。因此下面这段代码中的函数调用是错误的

void Fun(short &s) {
  cout << "Fun" << endl;
}
int iTest = 20;
Fun(iTest); //error:iTest是一个int变量,但是函数参数为short型的引用,需要系统产生一个short型的临时变量,但这不被允许
  • ++ --后置时:会生成一个临时对象tmp=原对象,再对原对象进行操作,但返回tmp
  • 对象间的隐式类型转换
string str; 
str=“abc”;
  • 使用push_back/insert且参数是右值时
struct Bar {
    Bar(int a) {}
    explicit Bar(int a, double b) {}
};

int main(void)
{
    vector<Bar> bv;
    bv.push_back(1);        // 隐式转换生成临时变量
    bv.push_back(Bar(1));   // 显示构造临时变量
    Bar b(1);
    bv.push_back(b);   // 没有临时变量
    bv.emplace_back(1);     // 没有临时变量

    //bv.push_back({1, 2.0});   //  无法进行隐式转换
    bv.push_back(Bar(1, 2.0));  //  显示构造临时变量
    bv.emplace_back(1, 2.0);    //  没有临时变量

    return 0;
}

补充:c++编译过程


https://www.cnblogs.com/ericling/articles/11736681.html

补充:编译器相关


概述

  • 编译器给对象分配内存的前提是知道其大小
  • 声明是给编译器用的,定义是给连接器用的

函数的编译

  • 代码中可以调用只声明未定义的函数,是因为在调用函数处,编译器会产生调用函数的代码,但编译器并不管函数的具体实现(即定义)在哪里,链接器才需要查找函数的实现代码并与函数调用代码对上;所以when we call a function, the compiler needs to see only a declaration for the function
  • 类似地,when we use objects of class type, the class definition must be available, but the definitions of the member functions need not be present
  • 所以we put class definitions and function declarations in header files and definitions of ordinary and class-member functions in source files(这样其他文件就可以只include .h文件)

模板的编译

  • 与普通函数不同,编译器会生成实例化后的模板的代码,所以the compiler needs to have the code that defines a function template or class template member function
  • 所以headers for templates typically include definitions as well as declarations
  • when the compiler sees the definition of a template, it does not generate code(但会检查模板的语法等);编译器 generates code only when we instantiate a specific instance of the template

补充:常函数


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