GeekBand Objective-C编程语言学习笔记(第一周)

第一天视频课程:


Objective-C 语言简介

Objective-C 语言是一门在C语言基础上做了面向对象扩展的编程语言,1983年由Brad Cox 和Tom Love发明,是目前苹果开发平台的主力语言,与Cocoa 和Cocoa Touch框架高度集成,支持开发Mac OS X、iOS应用。在苹果开发平台上通过LLVM(LowLevelVirtualMachine)编译器架构支持与Swift语言双向互操作。


iOS开发平台

Cocoa框架部分包括了系统内核(Core OS)、内核服务(Core Service)、媒体(Media)、触摸(Cocoa Touch)这几个内库。通过LLVM编译框架和Objective-C运行时编译和运行。目前主要有Objective-C、Swift和C/C++这几种语言来进行编码,常用的开发工具为Xcode。


掌握高级编程语言的思维方式

底层思维:从微观、机器的层面理解语言的构造、编译转换、内存模型和运行时机制。

抽象思维:将我们周围世界抽象为程序代码,即面向对象的思维方式,组件封装、设计模式、架构模式。


“时空人”三位一体分析方法

时间分析,发生在什么时候?编译时还是运行时。

空间分析,变量放在那里?堆空间还是栈空间。

人物分析,代码哪里来的?程序员还是编译器、运行时、框架。


Objective-C 语言的两种开发方式

Clang 或 GCC命令行:适合调试、研究、微观探查。

Xcode项目:适合构建正规工程项目,使用大型框架,追求设计质量与代码组织。


Objective-C 语言代码学习

#import 导入头文件(类似C语言的#include),可以避免相同头文件的重复导入,推荐使用#import代替#include。

@autoreleasepool 支持ARC(Automatic Reference Counting)的一个池,用来表明启用了内存自动回收机制。

NSlog(@"Hello,world!"); NSlog类似C语言里的printf用来打印字符串,OC语言里的字符串前需要加@符号(@"Hello,world!")。

 头文件的扩展名是.h,主程序文件的扩展名是.m

编译命名行:clang -fobjc-arc HelloWorld.m -o HelloWorld其中-fobjc-arc为ARC内存管理的开关命名,其中-o HelloWorld,-o 为output即输出的意思,一起为将输出的文件命名为HelloWorld。clang也可以换成gcc即用gcc编译器编译。(推荐用clang)

命名行:clang -help 用来显示clang的设置帮助文档,可以用来了解clang的设置。

命名行:./HelloWorld 执行生成的HelloWorld文件。


ObjC编译过程

目前主流为LLVM-Clang的编译过程,由OC、C\C++代码通过Clang前端再通过LLVM优化和LLVM代码生成器生成出X86-64机器码。


学习资源

苹果官方文档:https://developer.apple.com/library/

programming with Objective-C: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/

iOS专区:https://developer.apple.com/library/ios/

苹果开发者大会WWDC:https://developer.apple.com/videos/wwdc/2014/

                      WWDC:https://developer.apple.com/videos/wwdc/2015/


第二天视频课程:


类型系统


引用类型 reference type:包括class、pointer、block。

值类型 value type:包括基础数值类型、结构struct、枚举enum。

类型装饰:包括协议protocol、类别category、扩展extension


类 class VS. 结构 struct

类型与实例:类的实例是对象,结构的实例是值。

类是一个引用类型,是位于栈上的指针指向了一个位于堆上的实体对象。

结构是一个值类型,它直接位于栈中。

例子1:声明一个类(建立一个对外接口)

@interface RPoint:NSObject  //声明一个类,类名RPoint,继承自类NSObject。:表示继承。

@property int x; //声明一个属性 int X

@property int y;

-(void)print; //声明一个实例方法 print

@end

在OC中创建类时要先在头文件中(.h)创建一个对外借口,@interface来表示对外接口的起始,用@end来对应结束。

@property 在类中定义属性的关键字,用来表示这个类的状态。

-(void)print 是在类中定义方法的语句,其中-表示是一个实例方法,方法的返回值是void,方法的名字是print.类中的方法来表示这个类的行为。

例子2:实现一个类

#import<Foundation/Foundation.h> //导入Foundation.h头文件

#import"rpoint.h" //导入rpoint.h头文件,即上面我们创建的类声明文件。

@implementation RPoint  //定义一个类名为RPoint 的类

-(void)print{

NSLog(@"[%d,%d]", self.x, self.y);  //print方法的实现

}

@end

OC的类需要在主文件中(.m)实现,@implementation 来表示实现的起始,用@end来对应结束。

NSLog(@"[%d,%d]", self.x, self.y);  NSLog表示打印一个字符串,self表示当前的事例,整句语句表示将当前事例的属性x和y打印出来。

例子3:

#import<Foundation/Foundation.h>

#import"rpoint.h" //导入类所在的文件

#import"spoint.h"  //导入结构体所在的文件

void process(RPoint* rp3, SPoint sp3); //函数process的声明

int main(int argc, const char * argv[]){ //入口函数

    @autoreleasepool{

      RPoint* rp1=[[RPoint alloc]init]; //生成一个RPoint类的对象rp1

      rp1.x=10;

      rp1.y=20;

      [rp1 print]; //显示结果为10, 20

      SPoint sp1; //生成一个SPoint结构体实例。

      sp1.x=10;

      sp2.y=20;

      NSLog(@"拷贝----------");

      RPoint* rp2=rp1;

      rp2.x++;

      rp2.y++;

      [rp1 print]; //显示结果为11, 21

      [rp2 print]; //显示结果为11, 21

      SPoint sp2=sp1;

      sp2.x++;

      sp2.y++;

      NSLog(@"[%d,%d]",sp1.x, sp1.y); //显示结果为10, 20

      NSLog(@"[%d,%d]",sp2.x, sp2.y); //显示结果为11, 21

      NSLog(@"传参----------")

      process(rp1, sp1);

      [rp1 print]; //显示结果为12, 22

      NSLog(@"[%d,%d]",sp1.x, sp1.y); //显示结果为10, 20

    }

    return0;

}

void process(RPoint* rp3, SPoint sp3){

  rp3.x++;

  rp3.y++;

  sp3.x++;

  sp3.y++;

  [rp3 print];

  NSLog(@"[%d,%d]",sp3.x, sp3.y);

}

RPoint* rp1=[[RPoint alloc] init]; //在内存栈空间创建了一个名字为rp1的RPpoint类实例对象。[]为调用符号,调用也可以称为发送消息。alloc的用处是手动在堆空间申请内存空间,init为初始化所分配的空间。


拷贝行为内存视图


传参行为内存视图

堆(heap):堆空间用于存储引用类型对象,由程序员手动申请内存空间,释放由运行时ARC机制自动释放,函数之间通过拷贝引用(指针)传递。堆空间具有全局性,总体大小受制于系统内存整体大小。

由于rp1是一个类的实例对象,所以无论是复制还是传参,都是由一个栈中的指针指向堆中的实体对象,复制副本和传参参数的改变都能直接导致rp1指向的实际对象发生改变。

SPoint sp1; //在内存的堆空间创建了一个名字为sp1的结构体。

栈(stack):栈空间用于存储值类型,无ARC负担,由系统自动管理,以执行函数为单位。栈的空间大小在编译时确定(根据参数+局部变量来计算),在函数执行时由系统自动分配一个栈,函数执行结束系统立即自动收回该栈空间,函数之间通过拷贝值传递。栈空间具有局部性,大小有限额(编译工具可以设定栈的大小,一般为1M),超出会栈溢出(stack overflow)。

由于sp1是一个结构值,它存储于栈中,无论复制还是传参,都将在栈中复制一个sp1的副本,sp1的原始值保持不变。


第三天视频课程:


OC类的类型成员

OC类所包含的类型成员(Type Member)主要分为两大类,一类是数据成员(data member)用来描述对象的状态,还有一类是函数成员(function member)用来描述对象的行为。其中数据成员又有实例变量(instance variable)和属性(property)两种。函数成员分为方法(method)、初始化器(init)和析构器(dealloc)三种。

认识属性


属性表达实例状态,描述类型对外接口。相比直接访问实例变量,属性可以做更多控制。

默认情况下,编译器会为属性定义propertyName自动合成一个getter访问器方法:propertyName、一个setter访问器方法:setPropertyName还有一个实例变量_propertyName。

举例:

在OC类中声明一个属性:@property NSString* firstName; //声明一个类所包含的属性。

此时编译器会自动生成类似如下代码,但是并不显示出来:

-(NSString*)firstName{  //生成一个getter函数

   return _firstName;

}

-(void)setFirstName:(NSString *)newValue{  //生成一个setter函数

  _firstName=newValue;

}

NSString* _firstName; //创建一个以 _(下划线)+属性名的实例变量

由于编译器自动生成了类似如上代码,我们可以用编译器生成的getter方法和setter方法来访问或修改该属性的内容。

例如:

Employee* employee=[[Employee alloc] init];

[employee setFirstName: @"Tom"]; //将employee类的FirstName属性设置为“Tom”,setFirstName这个setter方法是编译器自动生成的。

NSLog(@"First Name: %@", [employee firstName]); //打印出employee类的FirstName属性,此处[employee firstName]是调用了编译器自动生成的getter方法firstName。

除了以上的方法来访问getter和setter方法外,可以用类名+.(点表达式)+属性名来访问属性的getter和setter方法。

例如:

employee.lastName=@"Chen"; //访问了lastName属性的setter表达式,等价于[employee setFirstName: @"Chen"];

NSLog(@"First Name: %@", employee.lastName); //访问了lastName属性的getter表达式,等价于NSLog(@"First Name: %@", [employee firstName]);

两种方法在本质上没有什么区别,推荐用点表达式,使用比较方便。


可自定义访问器方法,也可更改访问器方法名或实例变量名。

例如:

@property (readonly) NSString* fullName; //在头文件中定义了一个只读属性fullName

-(NSString *)fullName{

  return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];

} //在主文件中自定义了fullName属性的getter访问器方法。stringWithFormat函数是用来链接两个字符串类实例。

@property (getter=GivenName, setter=GivenName:) NSString* firstName; //声明firstName属性时将该属性的getter、setter访问器的名字都设置为GivenName。

@synthesize firstName=givenName; //在主文件中将firstName属性由系统自动生成的_firstName实例变量改名为givenName。

可以使用静态全局变量(C语言)+类方法,模拟类型属性。

static int _max=100; //在主文件中定义一个静态变量_max

+(int)max; //在头文件中声明一个类方法(getter访问器) +(int)max

+(void)setMax:(int)newValue; //在头文件中声明一个类方法(setter访问器)  +(void)setMax

+(int)max{

return _max;  //在主文件中实现类方法+(int)max

}

+(void)setMax:(int)newValue{

      _max=newValue;  //在主文件中实现类方法+(void)setMax

}

完成以上步骤后,可以实现访问类型属性。

[Employee setMax:400]; 等同于Employee.max=400; 模拟出来的类属性。


实例变量

可以定义实例变量,而不定义属性(这样外部不能访问到实例变量),只有实例变量,没有类变量(使用静态全局变量+类方法可以模拟出类变量的效果)。

如果同时自定义了getter和setter访问器方法,或者针对只读属性定义了getter访问器方法,编译器将不再合成实例变量。

引用类型的实例变量在类外一律使用属性来访问,类内大多也通过self使用属性访问。只有以下情就卡了况使用实例变量来访问:1、初始化器 init  2、析构器 dealloc 3、自定义访问器方法。 


实例变量的生存周期

实例变量的储存:跟随对象实例存储在堆上。

值类型实例变量直接“内嵌”在对象实例中。跟随对象实例内存释放而被释放。

引用类型实例变量通过指针“引用”堆上的引用类型实例,ARC针对引用进行计数管理,自动释放引用计数为0的对象。


属性的描述特性(Attribute)

属性的描述特性可以指定属性不同环境下的不同功能。

读写特性,默认情况属性都是可读写(readwrite)的,也可以设置为只读(readonly)属性。

例如:

@property (readonly) NSString* fullName; //在头文件中定义了一个只读属性fullName

多线程特性,默认情况下是属性是原子性(atomic)的,表示多线程时原子性属性不能被线程抢走,要么没有运行,开始运行了必须运行到结束。也可以设置为非原子性(nonatomic).

例如:

@property (nonatomic) NSString* fullName; //在头文件中定义了一个非原子性属性fullName


内存管理特性

引用属性默认为强(strong)引用属性,弱(weak)引用阻止循环引用。当两个实例对象属性相互强引用时会形成循环引用,这时ARC内存管理将视这两个互引用的属性都占用,无法将这两个实例的引用属性释放。

两个类型相互强引用导致内存泄露。

为了避免上述情况发生把其中一个对象属性设置为弱引用,这样就不会出现不能释放的问题了。


一个弱引用一个强引用就可以互相引用,并可以正常释放内存


拷贝(copy)属性创建独立拷贝,当引用属性不想被外界直接引用时,可以用拷贝属性,让外界引用一个拷贝副本,来确保原始数据不会被外界修改。

不使用拷贝特性时,被引用将多一条指针指向实例属性


使用拷贝特性后,被引用时多一条指针指向实例属性的副本


第四天视频课程:


认识方法 Method

代码段上的可执行指令序列就是函数,函数有全局函数(C语言函数),和成员函数(OC中也叫方法)。

方法是类的成员函数,表达实例行为或类型行为。

举例:

以下声明了4个方法

-(void) print; //-表示是实例方法,返回值是void, 方法名是print, 无参数

-(BOOL) isEqualToPoint: (BLNPoint*) point; //返回值是BOOL, 参数名是point, 参数类型是BLNPoint*

-(void) moveToX: (int)x toY: (int)y;  //有两个(int)类型的参数x和y

+(BLNPoint*) getOriginPoint; //+表示getOriginPoint是一个类方法

所有方法默认为公有方法。没有private或protected方法(可以在接口文件中不要声明方法,而在实现文件中实现方法来做到类似private方法)。

动态消息发布:方法调用通过运行时动态消息分发实现,在对象上调用方法又称“向对象发送消息”。

例子:

[p1 print]; //实例p1调用了print方法,也可以说向对象p1发送了一个print消息

[p1 moveToX:100 toY:200]; //向对象p1发送了一个moveToX消息,其中还包含了两个参数,100和200

BLNPoint* origin=[BLNPoint getOriginPoint]; //向BLNPoint类发送了一个getOriginPoint消息,返回值赋值给BLNPoint的类型实例origin


实例方法或类型方法

实例方法用来表达实例的行为所以只能通过实例对象来调用,实例方法在内部实现时可以访问实例成员包括实例属性、实例变量和实例方法。也可以访问类型方法和静态变量。

类方法用来表达类的行为只能通过类来调用,类型方法在实现时可以访问类型方法和静态变量,不能访问实例成员包括实例属性、实例变量和实例方法。

编译器背后对实例方法和类方法的不同处理:self指针

例子:

实例方法实现

-(void) print{

      NSLog(@"[%d, %d]", self.x, self.y);

}

上面的实例方法实现中self实际上是一个隐藏的指针参数,用来传递当前实例地址,编译器编译后用C语言的表达方式可以写成:

void print(BLNPoint* self){

      NSLog(@"[%d, %d]", self.x, self.y);

}

调用该方法时:[p1 print]; //print(p1);

类方法实现

+(BLNPoint*) getOriginPoint{

      BLNPoint* origin=[[BLNPoint alloc] init];

      origin.x=0;

      origin.y=0;

      return origin;

}

上述的类方法实现编译后用C语言表示为

BLNPoint* getOriginPoint(){

      BLNPoint* origin=[[BLNPoint alloc] init];

      origin.x=0;

      origin.y=0;

      return origin;

}

类方法里面不能用self关键字来访问实例变量,但是依然可以使用self关键字,这时self关键字用来表示当前类。在类方法实现里面可以用[self process]来调用当前类中的process方法,也等同于[BLNPoint process]。


方法参数

如果参数类型是值类型,为传值方式,如果参数类型为引用类型,则为传指针方式。

方法可以没有参数,也可以没有返回值。

如果方法有参数,方法名约定包含第一个参数,从第二个参数开始需要显示提供外部参数名。

-(void) moveToX: (int)x toY: (int)y; //toY是第二个参数的外部参数名,x是第一个内部参数名,y是第二个内部参数名。

调用时,第一个参数名忽略,但后面的参数名必须显示标明。如: [p1 moveToX:100 toY:200];


动态方法调用机制--消息分发表

在OC里所有的对象类型都可以声明为id类型

例子:

id obj=[[BLNPoint alloc] init];

[obj moveToX:50 toY:60];

[obj print];

这里类型为id的对象obj可以调用类型为BLNPoint的对象方法,是由于OC的动态调用机制造成的。


消息分发机制

OC中调用所有方法都会通过消息分发表,上图灰色的部分其中有一个指针,指向class再指向method list。这样做可以增加灵活性,支持在运行时向方法表添加新的方法。缺点是每次调用方法都要多次寻址有性能损失。


第五天视频课程:


初始化器与析构器

初始化器和析构器是类型的特殊函数成员,初始化器用于初始化对象实例或者类型。

对象初始化器:-(id)init 可以重载多个。

例子:

在头文件中可以声明多个有不同参数列表的-(id)init函数。

-(id)init;

-(id)initWithName:(NSString *)name;

-(id)initWithName:(NSString *)name WithPages:(int)pages;

-(id)initWithName:(NSString *)name WithPages:(int)pages WithCategory:(NSString *)category;

在主文件中实现声明的init方法

-(id)init{

      self = [super init];

      if(self){  //如果父类初始化失败self指针将等于Null

        NSLog(@"Book Object init“); //在调用了父类的init方法后可以添加自己所需的内容。

      }

      return self;

}

-(id)initWithName:(NSString *)name WithPages:(int)pages{

      return [self initWithName:name WithPages:pages WithCategory:@"General"];

}

-(id)initWithName:(NSString *)name WithPages:(int)pages WithCategory:(NSString *)category{ //由于该初始化器的参数是最多的,作为主初始化器,其他参数少的初始化器可以直接调用主初始化器。

      self = [super init];

      if(self){

      _name = [name copy]; //在初始化器中使用实例变量而不要使用实例属性。

      _pages = pages;

      _category = [category copy];

      }

      return self:

}

初始化对象实例时,init通常和alloc搭配使用。

Book *b1 = [[Book alloc]init]; //alloc是一个从NSObject继承过来的类方法。Tips: 按住commond健再点击关键词可以查看其类库。

Book *b1 = [[Book alloc]init]; 也可以拆分为:

Book *b1 = [Book alloc];

b1 =[b1 init]; //这里等号左边的b1是不可以省略的,因为[b1 init]有一个返回值,是返回一个地址,OC在这里返回的地址有可能和上一行Book *b1 = [Book alloc];其中的b1地址不一样。

alloc所做的事情--NSObject已实现:1、在对上分配合适大小的内存。 2、将属性或者实例变量的内存置0。

init所做的事情--可以自定义:1、调用父类初始化器[super init](前置调用)。 2、初始化当前对象实例变量。

Book *b1= [Book new]; //new相当于调用 alloc/init的无参数版本,不能传递参数。

类初始化器

类型初始化器:+(void)initialize 只能有一个,负责类型级别初始化,初始化类里面的静态变量。

initialize在每个类使用之前被系统自动调用,且每个进程周期中只被调用一次。

子类的initalize会自动调用父类的initialize(前置调用)。

+(void)initialize{

      if(self ==[Book class]){ //判断该类是否是Book类

            NSLog(@"Book Class initialize");

      }

}

对象析构器

对象析构器 -(void)dealloc 只能有一个,用于释放对象(没有类型析构器)拥有的动态资源,无返回值。

-(void)dealloc{

自动调用:1.ARC将对象属性引用计数减持(-1)

手动实现:2.释放不受ARC管理的动态内存,如malloc分配的内存,关闭非内存资源,如文件句柄、网络端口

自动调用:3.父类dealloc

}

dealloc由ARC根据对象引用计数规则,在释放对象内存前自动调用,无法手工调用。

子类的dealloc会自动调用父类的dealloc(后置调用)。


第六天视频课程:


认识面向对象

封装 encapsulation:隐藏对象内部实现细节,对外部仅提供公共接口访问。

继承 inheritance:一个类型在另外类型基础上进行的扩展实现。每一个类只能有一个基类,子类自动继承基类的:实例变量、属性、实例方法、类方法。NSObject类是所有类的根类,所有类向上追溯最上面的类都是NSObject类。继承有两层含义:1、成员复用,子类复用基类的成员。所有的成员都会被继承,就算是私有成员也被继承,只是成员访问不到。2、类型抽象,将子类当作父类来使用。

例子:

建立一个类Shape

@interface Shape : NSObject{ //Shape类继承于NSObject Tips:按住option键点击关键字可以看到相关的参考文档。

      @public int _data; //定义了一个公开的实例变量

}

      @property int no;

      -(void)draw;

      +(void)process;

@end

建立一个Shape的子类Circle

@interface Circle:Shape //Circle类继承了Shape类

@property int radius; //Circle类自己的实例属性

@end

由于Circle继承了Shape类,所以Shape类的实例变量、属性、实例方法、类方法Circle类都可以使用。

Circle* circle=[[Circle alloc]init];

circle.no=200;

circle->_data++; //在访问父类实例变量时用->来访问

[circle draw];

[Circle process]; //Circle类也可以调用父类的类方法。


Circle类继承Shape类的内存模型


第七天视频课程:


多态 polymorphism:不同类型针对同一行为接口的不同实现方式。

对比重写(override)与重载:子类重写父类同名同参数方法,子类只可以重写父类方法。方法名相同、参数不同,OC不支持方法的重载(重载指的是方法名相同,参数不同。用不同的参数传递给相同名字的方法,达到不同的运行效果)。

在子类的代码中,可以使用super类调用基类的实现,self具有多态性,可以指向不同子类,super没有多态性,仅指向当前父类。

例子:

建立一个Shape的子类Rectangle

@interface Rectangle:Shape

@property int width;

@property int length;

@end

基类Shape中有一个实例方法move:

-(void)move{

      NSLog(@"Shape object move");

      [self draw];

}

针对Rectangle类增加了两个属性(width和length)重写(override)继承过来的方法

@implementation Rectangle

-(id)init{

      self = [super init];  //重写时子类初始化器中必须首先调用基类的初始化器

      if(self){

            _length = 10;

            _width = 20;

      }

      return self;

}

-(void)draw{

      NSLog(@"Rectangle object draw: length=%d, width=%d", self.length, self.width);

}

-(void)print{

      NSLog(@"Rectangle Instance variable %d", _data);

}

+(void)process{

      NSLog(@"Rectangle class process");

}

-(void)dealloc{

       NSLog(@"Rectangle dealloc");

}

当实例对象调用时,Rectangle实例将调用重写的方法。Rectangle实例调用-(void)move时将调用父类Shape类的实例方法-(void)move,此方法中调用的draw方法[self draw],此时self有多态性,将调用Rectangle实例中的draw方法。

对于父类中的属性也可以在子类中重写。

父类Shape中的属性:@property int no;

在子类Rectangle中重写:

-(int)no{

      return super.no;  //super表示调用父类的方法

}

-(int)setNo:(int)no{

      super.no = no;

}

属性的重写本质上就是getter和setter两个访问器的重写。

继承中的init和dealloc

初始化器 init:子类自动继承基类的初始化器,子类也可以重写初始化器,重写时子类初始化器中必须首先调用基类的初始化器(手工调用)再添加其他内容。

析构器 dealloc:子类可以选择继续继承基类析构器,或者重写基类析构器,子类析构器执行完毕后,会自动调用基类析构器(后置调用,且不支持手工调用)。子类析构器自动具有多态性。

Tips:尽量避免在父类init和dealloc中调用子类重写的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容