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

第八天视频课程:

认识字符串 NSString

NSString是一个Unicode编码、16位字符的字符序列。

NSString被定义为类,引用类型,拷贝是具有引用语法。

初始化方法:

NSString *str1 = @"Hello World!"; \\字面量初始化,必须加@符号表示支持NSString是OC的字符串。

NSString *str2 =[[NSString alloc]initWithCString:"Hello World!" encoding:NSUTF8StringEncoding]; \\初始化器,分配内存,传递C语言的字符串,进行编码。

NSString *str3 =[NSString stringWithCString:"Hello World!" encoding:NSUTF8StringEncoding];工厂方法,工厂方法stringWithCString是一个类方法,返回了一个类对象。

NSString拥有恒定性(初始化之后值就不可以改变),所有的操作无法更改字符串本身,如有更改,都是返回新值的形式。

NSString拥有共享机制(由于NSString拥有恒定性,所以用字面量初始化方法(初始化器和工厂方法是动态创建方法这里没有共享机制效果)初始化的两个字符串的内同完全相同的话,这两个字符串将指向同一块堆空间,即使用同一块内存空间),引用技术管理对其有特殊的管理规则。

伪更改:返回新字符串

str1 = [str1 stringByAppendString:@''Yes!"]; //这句语句会申请一段新的内存,复制在原来str1字符串的内容,再在后面添加Yes!这几个字符,返回新的内存地址。原来的str1 地址指向的内存内容不变。

字符串之间判断相等:

如果要判断两个字符串的值是否相等要使用isEqualToString方法: [str1isEqualToString: str2] //比较str1和str2里面的值是否相等。如比较两个字符串指向的内存是否相等(指针是否相等)就用:str1 == str2来判断这两个字符串是否指向了同一块内存。

认识NSMutableString

NSMutableString具有可变性,NSString具有恒定性。

NSMutableString为NSString的子类

由于NSMutableString是NSString的子类可以吧一个NSMutableString类对象赋值给一个NSString类对象:

NSString *str5 = mustr1;

[mustr1 appendString:@"Very Good!"];

执行完上述操作将发现,NSString类对象str5也跟随NSMutableString类对象mustr1发生了改变,这样就违背了NSString的恒定性,这是一个类似漏洞的东西,以后要将NSMutableString类中的内容复制到NSString不可以直接赋值,而是要用stringCopy方法。

NSMutableString不具有共享机制,NSString具有共享机制。

NSMutableString并不是在原有内存上直接增长,而是重新分配一个更大或者更小的缓存容量存放字符

NSMutableString字符串初始化后,会分配一个缓存容量(capacity),其长度一般大于实际的字符串数量。当字符串增长时,如实际需求大于capacity,其capacity会以二倍的方式指数增长,伴随的代价:

分配新的堆内存 2*capacity

将原来堆内存上的内容拷贝到新内存

释放原来堆内存

最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长。

NSMutableString *mustr3 = [NSMutableString stringWithCapacity:100]; //在用工厂方法申请NSMutableString类对象时预留100个字符的缓存容量(capacity)。

字符串的长用方法

NSString:

访问字符串:

获取字符串字符:[str1 characterAtIndex:i];

字符串长度:[str1 length];

字面量:

大小写转换 :str1 = str1.uppercaseString;

str1 = str1.lowercaseString;

str1 = str1.capitalizedString;

查询字符串:

定位子串:NSRange range = [str1 rangOfString:@"Hello"];

NSLog(@"location:%lu, length:%lu, range.location, rang.length);

获取子串:NSString *substr = [str1 substringFromIndex:6];

是否包含子串:BOOL yOrN = [str1 hasPrefix:@"Hello"];

查询字符集:rangOfCharacterFromSet:

其他操作:

比较字符串:isEqualToString:

替换字符串:stringByReplacingOccurrencesOfString: withString

分解字符串:componentsSeparatedByCharactersInSet:

NSMutableString:

添加字符串:[mustr3 appendString:@"Hello Objective"];

删除字符串:[mustr3 insertString:@"-C" atIndex:mustr3.length];

修改字符串:[mustr3 setString:@"Hi Objective"];

NSRange replaceRange = NSMakeRange(0, 2);

[mustr3 relpaceCharactersInRange:replaceRange withString:@"Hello"];

NSRange deleteRange = NSMakeRange(5, 10);

[mustr3 deleteCharactersInRange:deleteRange];

第九天视频课程:

认识数组

数组是一个有序的元素序列,支持随机存取。索引从0开始,索引访问越界会抛出运行时异常。注意与C语言数组不同。

NSArray被定义为类,引用类型,拷贝时具有引用语义。

NSArray的初始化:

NSArray *array1 = [NSArray arrayWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris",nil]; //工厂方法初始化,最后要加nil,表示数组的结束。

NSArray *array2 = [[NSArray alloc]initWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris",nil]; //分配内存+初始化器,最后也要加nil做结束符

NSArray *array3 = @[Shanghai",@"Beijing",@"New York",@"Paris"]; //直接使用字面常量,在最前面要加@,结束时不需要加nil

NSArray的元素必须是对象,即NSObject的子类:

NSString *text = @"Panda"; //字符串对象

如为基本数值类型,须用NSNumber(继承于NSValue的类)封装为对象后,才能放入数组中。

NSInteger number = 100; // NSInteger是基本数值类型(整数),不是一个类

NSNumber *numberObject1 = [NSNumber numberWithInteger:number]; //使用NSNumber将NSInteger包装为对象

NSNumber *numberObject2 = @300u; //使用字面常量初始化

如为C语言结构类型(struct),须用NSValue封装为对象类型后,才能放入数组中。

Point point; //Point是一个结构类型(struct)

point.h = 100;

point.v = 200;

NSValue *pointObject = [NSValue value:&point withObjCType:@encode(Point)]; //使用NSValue将struct包装为对象

数组元素可以是不同对象类型,可能会有类型不安全。

NSNull *nullValue = [NSNull null]; //创建了一个空值类

NSArray *array4 = @[text, numberObject1, numberObject2, pointObject, nullValue ]; //创建的array4数组中大多的元素都是不同的对象类型

NSArray具有常量性:数组的长度和元素指针都不能更改。但指针指向的对象内部可以更改。

BLNPoint *p1=[[BLNPoint alloc] initWithX:10 WithY:20];

BLNPoint *p2=[[BLNPoint alloc] initWithX:20 WithY:40];

BLNPoint *p3=[[BLNPoint alloc] initWithX:30 WithY:60];

BLNPoint *p4=[[BLNPoint alloc] initWithX:40 WithY:80];

NSArray *array5=@[p1,p2,p3,p4];

NSLog(@"array5: %@", array5);

BLNPoint *p5=[[BLNPoint alloc] initWithX:50 WithY:100];

//1. 更改元素长度-----不可以!

//[array5 addObject:p5];

//2. 更改元素指针-----不可以!

//array5[0]=p5;

//3. 更改指针指向的内容-----可以!

for(int i=0;i

      BLNPoint *pt = array5[i];

      pt.x++;

      pt.y++;

}

NSLog(@"array5: %@", array5);

数组遍历

//快速枚举(fast enumeration),直接访问内存,优化索引检查,快5-10倍

for ( BLNPoint* point in array5)

{

      point.x++;

      point.y++;

}

//迭代器模式(NSEnumerator),索引检查+动态消息调用

NSEnumerator *enumerator = [array5 objectEnumerator];

BLNPoint* item;

while (item = [enumerator nextObject])

{

      item.x++;

      item.y++;

}

//for循环, 索引检查+动态消息调用

for (int i=0; i

      NSLog(@"array5[%d],%@", i, array5[i]);

}

推荐快速枚举的方法来遍历数组,快速枚举的方法较其他两种方法简化了索引检查和动态消息调用的次数,效率和性能较高。没有极特殊的理由不要用后两种方法来遍历数组。

数组查找

indexOfObject查找是否存在“值相等”的对象(类型需要实现isEqual)

- (BOOL)isEqual:(id)anObject{ //重写需要比较类的isEqual方法

      if (! [anObject isKindOfClass: [BLNPoint class]] ){  //isKindOfClass方法用来查看一个实例是不是属于某个类

            return false;

      }

BLNPoint* other=anObject;

return (self.x==other.x && self.y==other.y);

}

//开始查找数组

BLNPoint* target=[[BLNPoint alloc] initWithX:33 WithY:63];

NSUInteger index1=[array5 indexOfObject:target];

//按指针查找

indexOfObjectIdenticalTo查找是否存在“指针相等”的对象

NSUInteger index2=[array5 indexOfObjectIdenticalTo:p3];

数组排序

不改变原数组(常量性),返回新数组

NSArray* sortArray1=[array1 sortedArrayUsingSelector:@selector(compare:)]; //由于数组的常量性,排列后远数组不变,必须将返回值赋给一个新的数组

使用NSMutableArray数组

NSMutableArray支持更改数组长度和元素指针。为NSArray子类。

NSMutableArray初始化后,会分配一个缓存容量capacity,一般大于实际元素数量,当长度增长时,如实际需求大于capacity,其capacity会以近似两倍的方式指数增长。(和NSMutableString类似)

伴随代价:

分配新的堆内存 2*capacity

将原来堆内存上的内容拷贝到新内存

释放原来堆内存

最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长。

尽量避免使用insertObject: atIndex:和removeObjectAtIndex:等操作,因为会改变数组元素序列,涉及大量内存拷贝操作,代价高

NSMutableArray  *muArray1=[NSMutableArray arrayWithObjects:p1,p2,p3,p4, nil];

NSLog(@"before change, muArray1: %@",muArray1);

BLNPoint *p6=[[BLNPoint alloc] initWithX:60 WithY:120];

BLNPoint *p7=[[BLNPoint alloc] initWithX:70 WithY:140];

//修改元素

[muArray1 addObject:p5];

[muArray1 removeObjectAtIndex:2];

[muArray1 insertObject:p6 atIndex:1];

muArray1[0]=p7;  //[muArray1 setObject:p7 atIndexedSubscript:0]; //替换有两种方法,前一种方法比较方便

NSLog(@"after change, muArray1: %@",muArray1);

//预估容量

int count=100; NSMutableArray *muArray2=[NSMutableArray arrayWithCapacity:10];//编程时根据实际情况合理分配预估容量,尽量不要让capacity出现不够用儿扩容,那样代价比较大

for (int i=0; i< count; i++) {

      BLNPoint *pt=[[BLNPoint alloc] initWithX:i*10 WithY:i*20];

      [muArray2 addObject: pt];

}

认识Set

NSSet是一个无序的集合,其存储的对象不能重复。

NSSet被定义为class,引用类型,拷贝时具有引用语义。

常量集合NSSet,可变量集合:NSMutableSet:

常量性:长度和元素指针都不能更改,但指针指向的对象内部可以更改。

创建NSMutableSet时用initWithCapacity提前设置capacity

支持Fast Enumeration和NSEnumerator遍历,前者较快

NSSet *set1 = [NSSet setWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil];

NSLog(@"set1 count: %lu", set1.count);

NSMutableSet *set2 = [NSMutableSet setWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil];

[set2 addObject:@"London"];

[set2 addObject:@"Paris"];

[set2 removeObject:@"Beijing"];

NSLog(@"set2 count: %lu", set2.count);

if([set2 containsObject:@"Shanghai"])

{

      NSLog(@"set2 contains Shanghai");

}

for(NSString* item in set2)

{

      NSLog(@"%@", item);

}

认识Dictionary

NSDictionary是一个存储key-value的无序集合,key唯一,value可重复。

NSDictionary被定义为class,引用类型,拷贝时具有引用语义。

常量集合NSDictionary,可变量集合:NSMutableDictionary:

常量性:长度和元素指针都不能更改,但指针指向的对象内部可以更改。

创建NSMutableDictionary时用initWithCapacity提前设置capacity

支持Fast Enumeration和NSEnumerator遍历,前者较快

BLNPoint *p1=[[BLNPoint alloc] initWithX:10 WithY:20];

BLNPoint *p2=[[BLNPoint alloc] initWithX:20 WithY:40];

BLNPoint *p3=[[BLNPoint alloc] initWithX:30 WithY:60];

BLNPoint *p4=[[BLNPoint alloc] initWithX:40 WithY:80];

BLNPoint *p5=[[BLNPoint alloc] initWithX:50 WithY:100];

NSDictionary *dictionary1 = @{

@"Shanghai" : p1,

@"Beijing" : p2,

@"New York" : p3,

@"Paris" : p4};

NSMutableDictionary *dictionary2 = [NSMutableDictionary dictionaryWithObjectsAndKeys:

p1,@"Shanghai",

p2,@"Beijing",

p3,@"New York",

p4,@"Paris",

nil];

NSLog(@"dictionary1 count: %lu", dictionary1.count);

NSLog(@"dictionary2 count: %lu", dictionary2.count);

BLNPoint* result1=[dictionary1 objectForKey:@"Beijing"];

BLNPoint* result2=dictionary1[@"Shanghai"];

NSLog(@"%@", result1);

NSLog(@"%@", result2);

for(NSString* key in dictionary1)

{

      id object=dictionary1[key];

      NSLog(@"key:%@, object:%@", key, object);

}

[dictionary2 setObject:p5 forKey:@"London"];

[dictionary2 removeObjectForKey:@"Shanghai"];

NSLog(@"dictionary2: %@", dictionary2);

第十天视频课程:

了解ARC

自动引用计数器(Automatic Reference Counting)是Objective-C默认的内存管理机制,其针对堆上的对象,由编译器自动生成操作引用计数的指令(retain或release),来管理对象的创建和释放。

哪些对象受ARC管理:

OC对象指针

Block指针

使用_atteibute_((NSObject))定义的typedef

哪些对象不受ARC管理:

值类型(简单值类型,C语言struct)如int 、struct

使用其他方式分配的堆对象(如使用malloc分配)C/C++分配的堆内存也不受ARC管理

非内存资源

引用计数器管理

新创建(使用alloc, new, copy等)一个引用类型对象,引用计数为1

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

BLNRectangle *rect = [[BLNRectangle alloc]init];

对象引用计数器增加1--retain操作:

将对象引用赋值给其他变量或常量: BLNPoint *p2 = p1;

将对象引用赋值给其他属性或实例变量:rect.center = p1;

将对象传递给函数参数,或者返回值:draw(p1); //函数运行结束时ARC就会减1

将对象加入集合中:

array=[[NSMutableArray alloc]initWithCapacity:10];

[array addObject:p1];

对象引用计数减1--release操作:

将对象局部变量或者全局变量赋值为nil或其他值: p1 = nil; p2 = nil;

将属性赋值为nil或其他值:rect.center = nil;

实例属性所在的对象被释放:rect = nil;

参数或局部变量离开函数

将对象从集合中删除:[array removeObjectAtIndex:0];

引用计数变为0时,内存自动被释放。

引用计数的内存示意图,注意如果有两个对象属性互相引用,则要把一边设置为弱引用,以防ARC不能正确工作

自动释放池(Autorelease Pool)

release会导致对象立即释放。如果频繁对对象进行release, 可能会造成琐碎的内存管理负担。autorelease可以将release的调用延迟到自动释放池被释放时。

推荐使用自动释放池(Autorelease Pool)Block,当其结束时,所有接受autorelease消息的对象将会被立即释放(即发送release消息)。

AppKit和UIKit框架在处理每一次事件循环迭代时,都会将其放入一个Autorelease Pool中。大多数情况,无需程序员干预。

什么时候需要手动管理Autorelease Pool

编写的程序不基于UI框架,如命名行程序。

在循环中创建大量临时对象,需要更早的释放,避免临时对象聚集导致内存峰值过大。

void poolDemo(){

      @autoreleasepool {

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

                  __unused  BLNRectangle *rect = [[BLNRectangle alloc]init];

            }

      }

}

在主线程之外创建新的线程,在新线程开始执行处,需要创建自己的Autorelease Pool

可以嵌套使用Autorelease Pool

第十一天视频课程:

认识协议 Protocol

协议:类型的合同约定,只描述外部接口,不提供具体实现。也称作类型的行为约定。

协议可以包含以下成员:属性、实例方法、类方法、初始化器(不常用)、析构器(不常用),协议中无法包含实例变量成员。

//声明一个类并遵守一个协议

@interface BLNPoint : NSObject //<>里表示这个类遵守Drawable这个协议,即必须实现Drawable协议里全部的要求方法(@required)

@property  NSInteger x;

@property  NSInteger y;

@end

//声明一个类

@protocol Drawable

@property  NSInteger x;

@property  NSInteger y;

-(void)draw;

+(void)createShape;

//-(id)init;

//-(void)dealloc;

@optional //可选择方法关键字(@optional)遵守协议的类可实现也可以不实现下面的可选择方法,在@optional之上的都是要求方法(@required)

-(void)moveToX:(NSInteger)x withY:(NSInteger)y;

@end

协议中定义的属性本质上是访问器方法,编译器不会合成实例变量

使用协议

一个类遵守协议,需要实现协议约定的所有@required成员,协议中的属性须在实现类的.h文件中声明(编译器合成实例变量需要)协议不会实现实例变量

注意编译警告信息:

遵守协议后却没有实现全部必选协议方法(@required)时,会出现警告提示。

协议类型变量被赋值非协议类型对象时,会出现警告提示。

//实现一个参数遵守协议的函数process1

void process1(idobj)

{

      [obj draw];

      NSLog(@"[%ld,%ld]",(long)obj.x,(long)obj.y);

}

//调用process1函数

process1(@“abc”);//此时将出现警告,因为传递的@“abc”并没有遵守协议

协议本质上是一种类型,可以作为声明类型,但不能创建实例。

检查协议类型

使用conformsToProtocol:检查对象是否实现了协议

void process2(id obj){

if ([obj conformsToProtocol:@protocol(AProtocol) ]) {

      [obj methodA];

}

更多协议形式

协议继承

一个协议可以继承一个或多个协议

实现子协议的类型,也必须实现父协议中约定的成员

协议组合

可以使用protocol来组合多个协议

void process4(idobj){

      [obj methodA];

      [obj methodC];

}

实现组合协议的类型,必须实现组合协议中的每一个协议

可选协议

协议的某些成员可以定义为optional,不必实现

了解常用协议

NSObject:包含对象的常用操作,相等、字符串表示、哈希。

NSCopying:支持复制的类型必须遵守该协议。

NSMutableCopying:在NSCopying协议的基础上,支持复制数据的可变性。

NSFastEnumberation:实现快速枚举for-in的类型采用。

NSCoding协议:支持将对象图像进行编码/解码以支持对象序列化。

第十二天视频课程:

类别 Categroy

类别支持在没有源代码的情况下,基于某些特定的场合,为一个类增加功能。

可以添加:类方法、实例方法、重写基类方法

不能添加:属性、实例变量、已存在的同名方法。本质上类别不可以更改原来的内存模型。

可以使用原有类的实例变量,在类别中添加getter和setter访问器方法。

//NSObject类的声明

@interface BLNPoint : NSObject

{

      float _weight;

}

@property NSInteger x;

@property NSInteger y;

-(void)move;

@end

//在类别(Drawing)实现中加入BLNPoint类声明实例变量_weight的getter和setter访问器方法,等于把原来类中的实例变量,包装成了一个属性。

-(void)setWeight:(NSInteger)weight

{

      NSLog(@"Point.setWeight");

      _weight=weight;

}

-(NSInteger)weight

{

      NSLog(@"Point.weight");

      return _weight;

}

命名规范:类名+扩展方法,如:NSString+Drawing.h/.m

//  NSString类的类别Drawing的声明

@interface NSString(Drawing) //()内的Drawing就是类别

-(void)draw;

+(NSString*) convertToString:(NSInteger)number;

@end

类别的使用

使用场景

适合在没有源代码的情况下,向已经封装的类中添加方法。

为一个类在某些特殊场景下增加功能。

对于复杂的大型文件分割实现,同一个类的不同的类别可以放在不同的.m文件中实现,便于管理,但是不推荐将一个类扩充的特别大。

添加类别

自己创建类

系统的类

第三方库

扩展 Extension

扩展支持在编译时、有类的源代码的前提下,向类添加功能。可以将扩展看做匿名的类别。扩展没有.h文件对内可以访问,对外不公开不可以访问。(有点像私有成员)

接口定义在.m文件中@implementation前声明,实现代码仍然在@implementation内实现。

扩展支持添加一下成员:

添加属性

添加实例成员

添加类方法

添加实例方法

改写属性的读写属性(可以由readonly改成readwrite,反之不行)

//扩展的实现

@interface Circle () //扩展在原类名后加()

{

      NSString * _name;

}

@property (readwrite )NSInteger  radius;//修改读写属性

@property  NSInteger center;//添加属性

-(float)getDiameter;//实例方法

+(void)process:(Circle*) circle;//类方法

@end

@implementation Circle

+(void)process:(Circle*) circle

{

      [circle getDiameter];

      [circle getArea];

}

-(NSInteger)getArea{

      float area=M_PI*self.radius*self.radius;

      NSLog(@"Circle Area : %f",area);

      return area;

}

-(float)getDiameter{

      float diameter = 2*M_PI*self.radius;

      NSLog(@"Diameter : %f", diameter );

      return diameter;

}

@end

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

推荐阅读更多精彩内容