第3章:对象和类型

  • #1. 类和结构
  • #2. 类
    • 2.1 数据成员
    • 2.2 函数成员
    • 2.3 只读字段
  • #3. 匿名类型
  • #4. 结构
    • 4.1 结构是值类型
    • 4.2 结构和继承
    • 4.3 结构的构造函数
  • #5. 部分类
  • #6. 静态类
  • #7. Object类
    • 7.1 System.Object()方法
    • 7.2 ToString()方法

#1. 类和结构

类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法。类定义了类的每个对象(称为实例)可以包含什么数据和功能。

class PhoneCustomer {
    public const string DayOfSendingBill = "Monday";
    public int CustomerID;
    public string FirstName;
    public string LastName;
}

结构与类的区别是:它们在内存中的存储方式、访问方式(类是存储在堆(heap)上的引用类型),而结构是存储在栈(stack)上的值类型和它们的一些特征(如结构不支持继承)。较小的数据类型使用结构可提高性能。但在语法上,结构和类非常相似,主要区别是使用关键字struct代替class来声明结构。例如,如果希望所有的PhoneCustomer实例都分布在栈上,而不是在托管堆上,就可以编写下面的语句:

struct PhoneCustomerStruct {
    public const string DayOfSendingBill = "Monday";
    public int CustomerID;
    public string FirstName;
    public string LastName;
}

对于类和结构,都使用关键字new来声明实例:这个关键字创建对象并对其进行初始化。在下面的例子中,类和结构的字段值都默认为0:

PhoneCustomer myCustomer = new PhoneCustomer(); //works for a class
PhoneCustomerStruct myCustomer2 = new PhoneCustomerStruct(); //works for a struct

#2. 类

类中的数据和函数称为类的成员。Microsoft的正式术语对数据成员和函数成员进行了区分。除了这些成员外,类还可以包含嵌套的类型(如其他类)。成员的可访问性可以是public、protected、internal protected、private或internal。

2.1 数据成员

数据成员是包含类的数据——字段、常量和事件的成员。数据成员可以是静态数据。类成员总是实例成员,除非用static进行显示的声明。

字段是与类相关的变量。一旦实例化PhoneCustomer对象,就可以使用语法Object.FieldName来访问
这些字段,如下例所示:

PhoneCustomer customer1 = new PhoneCustomer();
customer1.FirstName = "Simon";

常量与类的关联方式同变量与类的关联方式。使用const关键字来声明常量。如果把它声明为public,就可以在类的外部访问它。

2.2 函数成员

函数成员提供了操作类中数据的某些功能,包括方法、属性、构造函数和终结器(finalizer)、运算符以及索引器。

  • 方法是与某个类相关的函数,与数据成员一样,函数成员默认为实例成员,使用static修饰符可以把方法定义为静态方法(类成员)。
  • 属性是可以从客户端访问的函数组,其访问方式与访问类的公共字段类似。C#为读写类中的属性提供了专用的语法。在客户端代码中,虚拟的对象被当做实际的东西。
  • 构造函数是在实例化对象时自动调用的特殊函数。它们必须与所属的类同名,且不能有返回类型。构造函数用于初始化字段的值
  • 终结器类似于构造函数,但是在CLR检测到不再需要某个对象的时候调用它。它们的名称与类相同,但前面有个“~”符号。不可能预测什么时候调用终结器。
  • 运算符执行的最简单的操作就是加法和减法。在两个整数相加时,严格来说,就是对整数使用“+”运算符。C#还允许指定把已有的运算符应用于自己的类(运算符重载)。
  • 索引器允许对象以数组或集合的方式进行索引。
1. 方法

注意,正式的C#术语区分函数和方法。在C#术语中,“函数成员”不仅包含方法,而且也包含类或结构的一些非数据成员,如索引器、运算符、构造函数和析构函数等,甚至还有属性。这些都不是数据成员,字段、常量和事件才是数据成员。

  • (1) 方法的声明

在C#中,方法的定义包括任意方法修饰符(如方法的可访问性)、返回值类型,然后依次是方法名和输入参数的列表(用圆括号括起来)和方法体(用花括号括起来)。

 [modifiers] return_type MethodName([parameters]) 
 {
    //Method body
 }

每个参数都包括参数的类型名和在方法体中的引用名称。但如果方法有返回值,return语句就必须与返回值一起使用,以指定出口点。例如:

public bool IsSquare(Rectangle rect) 
{
    return (rect.Height == rect.Width);    
}
  • (2) 给方法传递参数

参数可以通过引用或者通过值传递给方法。在变量通过引用传递给方法时,被调用的方法得到的就是这个变量,所以在方法内部对变量进行的任何改变在方法退出后仍旧有效。而如果变量通过值传递给方法,被调用的方法得到的是变量的一个相同副本。也就是说,在方法退出后,对变量进行的修改会丢失。对于复杂的数据类型,按引用传递的效率更高,因为在按值传递时,必须复制大量的数据。

在C#中,除非特别说明,所有的参数都通过值来传递。

static void SomeFunction(int[] ints, int i)
{
    ints[0] = 100;
    i = 100;
}

static void Main(string[] args)
{
    int i = 0;
    int[] ints = { 0, 1, 2, 4, 8 };
    //Display the orginal values
    Console.WriteLine("i = " + i);
    Console.WriteLine("ints[0] = " + ints[0]);
    Console.WriteLine("Calling SomeFunction");

    //After this method returns,ints will be changed,
    //but i will not.
    SomeFunction(ints, i);
    Console.WriteLine("i = " + i);
    Console.WriteLine("ints[0] = " + ints[0]);
}
  • (3) ref参数

如前所述,通过值传送变量是默认的,也可以迫使值参数通过引用传送给方法。为此,要使用ref关键字。如果把一个参数传递给方法,且这个方法的输入参数前带有ref关键字,则该方法对变量所做的任何改变都会影响原始对象的值:

static void SomeFucntion(int[] ints, ref int i) {
    ints[0] = 100;
    i = 100;//The change to i will persist after SomeFunction() exist.
}

在调用方法时,还需要使用ref关键字:

SomeFucntion(ints, ref i);

最后,C#仍要求对传递给方法的参数进行初始化,理解这一点也非常重要。在传递给方法之前,无论是按值传递,还是按引用传递,任何变量都必须初始化。

  • (4) out参数

在C风格的语言中,函数常常能从一个例程中输出多个值,这使用输出参数实现。只要把输出的值赋予通过引用传递给方法的变量即可。通常,变量通过引用传递的初值并不重要,这些值会被函数重写,函数甚至从来没有使用过它们。

编译器使用out关键字来初始化。在方法的输入参数前面加上out前缀时,传递给该方法的变量可以不初始化。该变量通过引用传递,所以在从被调用的方法中返回时,对应方法对该变量进行的任何改变都会保留下来。在调用该方法时,还需要使用out关键字,与在定义该方法时一样。

static void SomeFunction(out int i)
{
    i = 100;
}
static void Main(string[] args)
{
    int i; //note how i is declared but not initialized.
    SomeFunction(out i);
    Console.WriteLine(i);
}
  • (5) 命名参数

参数一般需要按定义的顺序传送给方法。命名参数允许任意顺序传递。所以下面的方法:

static string FullName(string firstName,string lastName) {
    return firstName + " " + lastName;
}

下面的方法调用会返回相同的全名:

Console.WriteLine(FullName("henry","hu"));
Console.WriteLine(FullName(lastName:"hu",firstName:"henry"));

如果方法有几个参数,就可以在同一个调用中混合使用位置参数和命名参数。

  • (6) 可选参数

参数也可以是可选的。必须为可选参数提供默认值。可选参数还必须是方法定义的最后一个参数。所以下面的方法声明是不正确的:

void TestMethod(int optionalNumber = 10,int notOptionalNumber)
{
    Console.Write(optionalNumber + notOptionalNumber);
}

要使这个方法正常工作,就必须在最后定义optionalNumber参数。

  • (7) 方法的重载

C#支持方法的重载——方法的几个版本有不同的签名(即,方法名相同,但参数的个数和/或类型不同)。为了重载方法,只需声明同名但参数个数或类型不同的方法即可。

class ResultDisplayer
{
    void DisplayResult(string result)
    {
        //impletation
    }

    void DisplayResult(int result)
    {
        //impletation
    }
}

在任何语言中,对于方法重载,如果调用了错误的重载方法,就有可能出现运行错误。现在,知道C#在重载方法的参数方面有一些小限制即可:

  • 两个方法不能仅在返回类型上有区别。
  • 两个方法不能仅根据参数是声明为ref还是out来区分。
2. 属性

属性(property)的概念是:它是一个方法或一对方法,从客户端代码看来,它们是一个字段。例如Windows窗体的Height属性。假定有下面的代码:

// mainForm is of type System.Windows.Forms
mainForm.Height = 400;

在语法上,上面的代码类似于设置一个字段,但实际上是调用了属性访问器,它包含的代码重新设置了窗体的大小。

在C#中定义属性,可以使用下面的语法:

public string SomeProperty 
{
    get 
    {
        return "This is the property value."
    }
    set 
    {
        //do whatever needs to be done to set the property.
    }
}

get访问器不带任何参数,且必须返回属性声明的类型。也不应该为set访问器指定任何显示参数,但编译器假定它带一个参数,其类型也与属性相同,并表示为value。

  • (1) 只读和只写属性

在属性定义中省略set访问器,就可以创建只读属性。因此,如下代码把Name变成只读属性:

private string name;

public string Name
{
    get 
    {
        return name;
    }
}

同样,在属性定义中省略get访问器,就可以创建只写属性。

  • (2) 属性的访问修饰符

C#允许给属性的get和set访问器设置不同的访问修饰符,所以属性可以有公有的get访问器和私有或收保护的set访问器。这有助于控制属性的设置方式或时间。

public string Name 
{
    get 
    {
        return _name;
    }
    private set 
    {
        _name = value;
    }
}

上述代码示例中,set访问器有一个私有访问修饰符,而get访问器没有任何访问修饰符。这表示get访问器具有属性的访问级别。在get和set访问器中,必须有一个具备属性的访问级别。如果get访问器的访问级别是protected,就会产生一个编译错误,因为这会使两个访问器的访问级别都不是属性。

  • (3) 自动实现的属性

如果属性的set和get访问器中没有任何逻辑,就可以使用自动实现的属性。这种属性会自动实现后备成员变量。

public int Age {get; set;}

不需要声明private int age。编译器会自动创建它。使用自动实现的属性,就不能在属性设置中验证属性的有效性。所以在上面的例子中,不能检查是否设置了无效的年龄。但必须有两个访问器。尝试把该属性设置为只读属性,就会出错:

public int Age {get;}

但是,每个访问器的访问权限可以不同。因此,下面的代码是合法的:

public int Age {get; private set;}
  • (4) 内联

C#代码会编译为IL,然后在运行时JIT编译为本地可执行代码。JIT编译器可生成高度优化的代码,并在适当的时候随意地内联代码(即,用内联代码来代替函数调用)。如果实现某个方法或属性仅是调用另一个方法,或返回一个字段,则该方法或属性肯定是内联的。但要注意,在何处内联代码完全由CLR决定。我们无法使用像C++中inline这样的关键字来控制哪些方法是内联的。

3. 构造函数

声明基本构造函数的语法就是声明一个与包含的类同名的方法,但该方法没有返回类型:

public class MyClass 
{
    public MyClass() 
    {
        
    }
    //rest of class definition
}

一般情况下,如果没有提供任何构造函数,编译器会在后台创建一个默认的构造函数。这是一个非常基本的构造函数。它只能把所有的成员字段初始化为标准的默认值。

  • (1) 静态构造函数

C#的一个新特征是也可以给类编写无参数的静态构造函数。这种构造函数只执行一次,而前面的构造函数是实例构造函数,只要创建类的对象,就会执行它。

class MyClass 
{
    static MyClass() 
    {
        //initialization code
    }
    //rest of class definition
}

编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。

.Net运行库没有确保什么时候执行静态构造函数,所以不应把要求某个特定时刻(例如,加载程序集时)执行的代码放在静态构造函数中。也不能预计不同类的静态构造函数按照什么顺序执行。但是,可以确保静态构造函数至多运行一次,即在代码引用类之前调用它。在C#中,通常在第一次调用类的任何成员之前执行静态构造函数。

注意,静态构造函数没有访问修饰符,其他C#代码从来不调用它,但在加载类时,总是由.NET运行库调用它,所以访问修饰符没有任何意思。处于同样原因,静态构造函数不能带任何参数,一个类也只能有一个静态构造函数。很显然,静态构造函数只能访问类的静态成员,不能访问类的实例成员。

public class UserPreference 
{
    public static readonly Color BackColor;
    //Static constructor
    static UserPreference() 
    {
        DataTime now = DateTime.Now;
        if(now.DayOfWeek == DayOfWeek.Saturday 
            || now.DayOfWeek == DayOfWeek.Sunday) 
        {
            BackColor = Color.Green;    
        }
        else 
        {
            BackColor = Color.Red;
        }
    }
    //Instance constructor
    private UserPreference()
    {
        
    }
}
  • (2) 从构造函数中调用其他构造函数

有时,在一个类中有几个构造函数,以容纳某些可选参数,这些构造函数包含一些共同的代码。例如,下面的情况:

class Car 
{
    private string description;
    private uint nWheels;
    public Car(string description,uint nWheels) 
    {
        this.description =  description;
        this.nWheels = nWheels;
    }
    public Car(string description) 
    {
        this.description = description;
        this.nWheels = 4;
    }
}

这两个构造函数初始化了相同的字段,显然,最好把所有的代码放在一个地方。C#有一个特殊的语法,称为构造函数初始化器,可以实现此目的:

class Car 
{
    private string description;
    private uint nWheels;
    public Car(string description,uint nWheels) 
    {
        this.description =  description;
        this.nWheels = nWheels;
    }
    public Car(string description) : this(description,4)
    {
    }
    //etc.
}

这里,this关键字仅调用参数最匹配的那个构造函数。注意,构造函数初始化器在构造函数的函数体之前执行。现在假定运行下面的代码:

Car myCar = new Car("Proton Persona");

在本例中,在带一个参数的构造函数的函数体执行之前,先执行带两个参数的构造函数(但在本例中,因为在带一个参数的构造函数的函数体中没有代码,所以没有区别)。

C#构造函数初始化器可以包含对同一个类的另一个构造函数的调用,也可以包含对直接基类构造函数的调用(使用相同的语法,但应使用base关键字代替this)。初始化器中不能有多个调用。

2.3 只读字段

常量的概念就是一个包含不能修改的值的变量,常量是C#与大多数编程语言共有的。但是,常量不必满足所有的要求。

readonly关键字比const关键字灵活得多,允许把一个字段设置为常量,但还需要执行一些计算,以确定初始值。其规则是可以在构造函数中给只读字段赋值,但不能在其他地方赋值。 只读字段还可以是一个实例字段,而不是静态字段,类的每个实例可以有不同的值。与const字段不同,如果要把只读字段设置为静态,就必须显示声明它。


#3. 匿名类型

var关键字可用于表示隐式类型化的变量。var和new关键字一起使用时,可以创建匿名类型。匿名类型只是一个继承Object且没有名称的类。该类的定义从初始化器中推断,类似于隐式类型化的变量。

如果需要一个对象包含某个人的姓氏、中间名和名字,则声明如下:

var captain = new {FirstName = "James", MiddleName = "T", LastName = "Kirk"};

这会生成一个包含FirstName、MiddleName和LastName属性的对象。


#4. 结构

有时仅需要一个小的数据结构。此时,类提供的功能多于我们需要的功能,由于性能原因,最好使用结构。看看下面的例子:

class Dimensions 
{
    public double Length;
    public double Width;
}

上面的代码定义了类Dimensions,它只存储了某一项的长度和宽度。有时我们需要存储轻量级的数据,并不需要class所带来的复杂功能。

为此,只需要修改代码,用关键字stuct代替class,定义一个结构而不是类,如本章前面所述:

struct Dimensions 
{
    public double Length;
    public double Width;
}

为结构定义函数与为类定义函数完全相同。下面代码说明了结构的构造函数和属性:

struct Dimensions 
{
    public double Length;
    public double Width;
    
    public Dimensions(double length,double width) 
    {
        Length = length;
        Width = width;
    }
    
    public double Diagonal 
    {
        get 
        {
            return Math.Sqart(Length*Length + Width*Width);
        }    
    }
}

结构是值类型,不是引用类型。它们存储在栈中或存储为内联(inline)(如果它们是存储在堆中的另一个对象的一部分),其生存期的限制与简单的数据类型一样。

  • 结构不支持继承。
  • 对于结构构造函数的工作方式有一些区别。尤其是编译器总是提供一个无参数的默认构造函数,它是不允许替换的。
  • 使用结构,可以指定字段如何在内存中的布局。

因为结构实际上是把数据项组合在一起,有时大多数或者全部字段都声明为public。

4.1 结构是值类型

虽然结构是值类型,但在语法上常常可以把它们当作类来处理。例如,在上面的Dimensions类的定义中,可以编写下面的代码:

Dimensions point = new Dimensions();
point.Length = 3;
point.Width = 6;

注意,因为结构是值类型,所以new运算符与类和其他引用类型的工作方式不同。new运算符并不分配堆中的内存,而是只调用相应的构造函数,根据传送给它的参数,初始化所有的字段。对于结构,可以编写下述完全合法的代码:

Dimensions point;
point.Length = 3;
point.Width = 6;

如果Dimensions是一个类,就会产生一个编译错误,因为point包含一个未初始化的引用——不指向任何地方的一个地址,所以不能给其字段设置值。但对于结构,变量声明实际上是为整个结构在栈中分配空间。所以就可以为它赋值了。但要注意下面的代码会产生一个编译错误,编译器会抱怨用户使用了未初始化的变量:

Dimensions point;
Double D = point.Length;

结构遵循其他数据类型都遵循的规则:在使用前所有的元素都必须进行初始化。在结构上调用new运算符,或者给所有的字段分别赋值,结构就完全初始化了。当然,如果结构定义为类的成员字段,在初始化包含的对象时,该结构会自动初始化为0。

结构是会影响性能的值类型,但根据使用结构的方式,这种影响可能是正面的,也可能是负面的。

  • 正面的影响是为结构分配内存和销毁时,速度非常快。因为它们将内联或者保存在栈中。
  • 负面的影响是,只要把结果作为参数来传递或者把一个结构赋予另一个结构(如A=B,其中A和B是结构),结构的所有内容就被复制。

4.2 结构和继承

结构不是为继承设计的。这意味着:它不能从一个结构中继承。唯一的例外是对应的结构(和C#中的其他类型一样)最终派生于类System.Object。结构的继承链是:每个结构派生自System.ValueType类,System.ValueType类又派生自System.Object。

4.3 结构的构造函数

为结构定义构造函数的方式与为类定义构造函数的方式相同,但不允许定义无参数的构造函数。这看起来似乎没有意义,其原因隐藏在.NET运行库的实现方式中。下述情况非常少见:.NET运行库不能调用用户提供的自定义无参数构造函数。因此Microsoft采用一种非常简单的方式,禁止在C#结构内使用无参数的构造函数。

前面说过,默认构造函数把数值字段都初始化为0,把引用类型初始化为null,且总是隐式地给出,即使提供了其他参数的构造函数,也是如此。提供字段的初始值也不能绕过默认构造函数。下面的代码会产生编译错误:

struct Dimensions 
{
    public double Length = 1; //error. Initial Values not allowed
    public double Width = 2; //error. Initial Values not allowed
}

#5. 部分类

partial关键字允许把类、结构和结构放在多个文件中。一股青况下,=个类全部驻留在单个文件中。但有时,多个开发人员需要访问同一个类,或者某种类型的代码生成器生成了一个类的某部分,所以把类放在多个文件中是有益的。

partial关键字的用法是:把partial放class、struct或interface关键字前面。在下面的例子中,
TheBigClass类驻留在两个不同的源文件BigClassPart1.cs和BigClassPart2.cs中:

//BigClassPart1.cs
partial class TheBigClass 
{
    public void MethodOne() 
    {
        
    }
}
//BigClassPart2.cs
partial class TheBigClass 
{
    public void MethodTwo() 
    {
        
    }
}

编译包含这两个源文件的项目时,会创建一个TheBigClass类,它有两个方法MethodOne()和MethodTwo()。


#6. 静态类

如果类只包含静态的方法和属性,该类就是静态的。静态类在功能上与使用私有静态构造函数创建的类相同。不能创建静态类的实例。使用static关键字,编译器可以检查用户是否不经意间给该类添加了实例成员。如果是,就生成一个编译错误。这可以确保不创建静态类的实例。

static class StaticUtilities 
{
    public static void HelperMethod() 
    {
        
    }
}

调用HelperMethod不需要StaticUtilities类型的对象。使用类型名即可以进行该调用:

StaticUtilities.HelperMethod();

#7. Object类

所有的.NET类都派生自System.Object。实例上,如果在定义类时没有指定基类,编译器就会自动假定这个类派生自Object。

其实际意义在于,除了自己定义的方法和属性等外,还可以访问为0刂ect定义的许多公有的和受保护的成员方法。这些方法可用于自己定义的所有其他类中。

7.1 System.Object()方法

下面将简要总结每个方法的作用:

  • ToString():是获取对象的字符串表示的一种便捷方式。当只需要快速获取对象的内容,以进行调试时,就可以使用这个方法。
  • GetHashCode():如果对象放在名为映射(也称为散列表或字典)的数据结构中,就可以使用这个方法。
  • Equals()和ReferenceEquals():如果把3个用于比较对象相等性的不同方法组合起来,就说明 .NET Framework在比较相等性方面有相当复杂的模式。
  • Finalize():它最接近C++风格的析构函数,在引用对象作为垃圾被回收以清理资源时调用它。Finalize()的Object实现方式实际上什么也没有做,因而被垃圾收集器忽略。如果对象拥有未托管资源的引用,则在该对象被删除时,就需要删除这些引用,此时一般要重写Finalize()。
  • GetType():这个方法从System.Type派生的类的一个实例。这个对象可以提供对象成员所属类的更多信息,包括基本类型、方法、属性等。System.Type还提供了 .NET的反射技术的入口点。
  • MemberwiseClone():这是System.Object中唯一没有在本书的其他地方详细论述的方法。因为它在概念上相当简单,它只复制对象,并返回对副本的一个引用(对于值类型,就是一个装箱的引用)。

7.2 ToString()方法

ToString()方法是快速获取对象的字符串表示的最快捷的方式。

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

推荐阅读更多精彩内容