【知识点】对象在内存中的存储,new的作用?
Persion *p1 = [Person new]
new做了哪些事?
1、在堆内存中申请一块合适大小的空间
2、根据类的模板在空间中创建对象(创建类模板的属性和isa指针,isa指针指向代码中的类)
3、初始化对象的属性(如果对象属性的数据类型是C数据类型,初始化为Null,如果是OC数据类型,初始化为Nil)
4、返回创建对象的地址
【知识点】nil与Null的相同点与不同点?
相同点:
1、都只能作为指针变量来使用,都表示不指向内存中的任何空间;
2、在值上是等价的,都是0
不同点:
Nil:用于表示指向 Objective-C 对象的空指针
Null:用于表示普通的 C 指针的空值。
(10)异常处理
什么是异常?
程序可以编译 链接 执行.
当程序在执行的时候 处于某种特定条件下 程序的执行就会终止.
异常的后果: 程序会立即崩溃.程序立即终止运行.并且后面的代码不会执行了
处理异常:try……catch
@try
{
}
@catch(NSException *ex)
{
}
避免异常最常用的还是逻辑判断:
如:判断某个对象中是否包含某个方法
// [对象 respondsToSelector:@selector(方法)]
[xiaoMing respondsToSelector:@selector(showFist)];
包含回返回1,不包含返回0
(11)类方法
分类:
OC中的方法分为两类:
1、对象方法/实例方法;(调用时,必须先创建对象,然后通过对象名调用)
2、类方法;(调用不依赖对象,可直接通过类名调用)
声明:
对象方法的声明:
-(返回值类型)方法名;
-(void)showFist;
类方法的声明:
+(返回值类型)方法名;
+(void)saiHi;
类方法和对象方法的声明和实现,除了-和+,其他一致;
调用:
但调用时有区别;
对象方法,必须通过实例化对象调用;即之前用的[Persopn *p1] = [Person new] [p1 showFist]
但类方法不依赖对象,可直接通过类名调用:[Person sayHi]
类方法的特点:
类方法因为可以直接调用方法,因此较对象方法来讲更节省空间且更高效
缺点:
1、类方法中不能像对象方法一样直接访问类的属性;
因为类调用方法,是直接在内存中找到存储该类的代码段,然后执行方法;而类的属性,是在实例化的时候才会创建(参考知识点new的作用),所以类方法无法直接访问类的属性;
//---------------------接口
@interface Person : NSObject{
@public
NSString *_name;
}
//类方法声明
+(void)sayHi;
@end
//---------------------实现
@implementation Person
+(void)sayHi{
_name; //不能直接访问或修改类的属性
NSLog(@"我是类方法");
}
@end
2、类方法无法通过self调用同一个类的对象方法(因为没有创建对象);
注意:
虽然类方法无法直接调用类的属性和对象方法,但对象方法可以直接调用类方法;
什么时候用类方法:
在这个方法中,不需要直接访问类的属性和对象方法时,可以使用类方法,以此节省空间,提升效率;
类方法的规范:
1、如果要实现一个类,则要求为这个类创建一个和类同名的类方法,然后这个方法创建一个纯洁的对象并返回;
例如:我们要创建一个Person类,则按照规范,我们需要创建一个和Person类同名的类方法,即:
//---------------------接口
@interface Person : NSObject{
@public
NSString *_name;
}
//类方法声明
+(void)sayHi;
//纯洁的类方法
+(Person *)person;
@end
//---------------------实现
@implementation Person
+(void)sayHi{
_name; //不能直接访问或修改类的属性
NSLog(@"我是类方法");
}
//纯洁的类方法,创建一个对象,然后返回
+(Person *)person{
Person *p1 = [Person new];
return p1;
}
@end
这样,在为这个类创建对象时,有以下两种方式:
Person *p1 = [Person new];
//或
Person *p2 = [Person person];
如果你希望对象一创建就携带了类的属性,那么可以定义一个带参数的类方法:
//---------------------接口
@interface Person : NSObject{
@public
NSString *_name;
int _age;
}
//类方法声明
+(void)sayHi;
//与类同名的类方法
+(Person *)person;
//带参数的类方法
+(Person *)personWithNameAge:(NSString *)userName andAge:(int)userAge;
@end
//---------------------实现
@implementation Person
+(void)sayHi{
NSLog(@"我是类方法");
}
//与类同名的类方法
+(Person *)person{
Person *p1 = [Person new];
return p1;
}
//带参数的类方法
//方法名规范:类名Withxxxx
+(Person *)personWithNameAge:(NSString *)userName andAge:(int)userAge{
Person *p1 = [Person new];
p1->_name = userName;
p1->_age = userAge;
return p1;
}
@end
调用时,创建出的对象就直接带上了一些属性值;
Person *p1 = [Person personWithNameAge:@"小米" andAge:18];
NSLog(@"%d",p1->_age); //18
与原来的先创建对象,再给对象属性赋值的写法更简便:
Person *p1 = [Person new];
p1->_name = @"小米";
p1->_age = 20;
NSLog(@"%d",p1->_age); //20
(12)NSString
定义:
NSString是一个Foundation框架提供的类,用来存储OC中的字符串,因此本质上来讲创建出的字符串其实是一个NSString对象;
标准的创建方法:
NSString *str1 = [NSString new];
//按照苹果的规范,苹果还提供了一个同名类方法来创建对象,即:
NSString *str2 = [NSString string];
通过上述的标准创建方式,可以创建出一个空的NSString字符串:@""
简便的创建方法:
NSString *str = @"";
NSString *str1 = @"测试";
最常用的类方法:
(1)将C语言字符串转换为OC字符串:stringWithUTF8String
char *c1 = "rose";
NSString *ns = [NSString stringWithUTF8String:c1];
NSLog(@"%@",ns);
(2)格式化字符串:stringWithFormat
NSString *ns = [NSString stringWithFormat:@"格式化字符串,测试人员:%@,年龄:%d",@"大王",20]; //格式化字符串,测试人员:大王,年龄:20
NSLog(@"%@",ns);
(3)将字符串写入指定的文件中:writeToFile
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile encoding:(NSStringEncoding)enc error:(NSError **)error;
BOOL res =[str writeToFile:@"/Users/babylon/Desktop/code/day9_oc/day09/day09/haowai.txt" atomically:NO encoding:NSUTF8StringEncoding error:&err];
//第一种判断方式:
NSLog(@"%@",res==YES?@"写入成功!":@"写入失败!");
path:要写入的路径
atomically:保证原子性,如果传入yes,会先创建一个临时文件,如果写入成功之后,再将临时文件拷贝到指定目录;安全不会出错,但效率极低;反之,如果传NO,则直接写入指定目录,有可能会出错,但效率高。推荐使用NO;
encoding:以哪种编码形式将数据写入文件,如:NSUTF8StringEncoding
error:如果方法执行成功.则方法执行完毕之后. 这个 NSError 的指针的值为 nil 如果方法执行失败.则方法执行完毕之后 这个 NSErro 指针会指向 1 个错误对象. 可以打印这个错误对象得到发生错误的详细信息
(4)将文件中内容读取到字符串:stringWithContentsOfFile
+ (nullable instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
NSError *err;
//将文件中的内容读取到字符串中.
NSString * str =[NSString stringWithContentsOfFile:@"/Users/babylon/Desktop/code/day9_oc/day09/day09/haowai.txt" encoding:NSUTF8StringEncoding error:&err];
if (err != Nil) {
NSLog(@"%@",err.localizedDescription);
}
else{
NSLog(@"%@",str);
}
(5)从URL中读取内容到字符串:stringWithContentsOfURL
字符串不仅可以从本地读取文件,也可以从URL中读取文件
详细参见目录:NSURL
常用的对象方法:
(1)获取字符串长度:length
返回值类型为:NSUInteger,long型
NSString *ns = @"test this string how long";
NSUInteger len = [ns length]; //或直接ns.length
NSLog(@"字符串长度为:%ld",len);
(2)获取字符串指定下标的字符:characterAtIndex
返回值类型为:unichar,字符串型,输出时使用%C
NSString *ns = @"test this string how long";
unichar ch = [ns characterAtIndex:6];
NSLog(@"%C",ch); //h
(3)比较两个字符串是否相等:isEqualToString
返回值类型为:BOOL,输出时可用%d
NSString *str1 = @"哈哈哈哈";
NSString *str2 = @"嘻嘻嘻嘻";
BOOL res = [str1 isEqualToString:str2];
NSLog(@"%d",res); //0
不能使用==来比较,不严谨。
(4)比较字符串大小:compare
返回值类型为NSComparisonResult,枚举类型,-1小于,0等于,1大于
NSString *str1 = @"China";
NSString *str2 = @"China";
NSComparisonResult res = [str1 compare:str2];
NSLog(@"%ld",res); //0
忽略大小写比较:传入compare的第二个参数options:NSCaseInsensitiveSearch
NSString *str1 = @"Jack";
NSString *str2 = @"jack";
NSComparisonResult res1 = [str1 compare:str2 options:NSCaseInsensitiveSearch];
NSLog(@"%ld",res1); //0 相等
options的其他传参:
NSCaseInsensitiveSearch:忽略大小写
NSNumericSearch:适合字符串格式相同时,其中数字的比较,如比较pic00872.png pic00873.png
(5)判断字符串是否以指定的字符串开头:hasPrefix
NSString *str1 = @"Jackhhhh";
BOOL include = [str1 hasPrefix:@"Jack"];
NSLog(@"是否以Jack开头:%d",include); //是为1,否为0
(6)判断字符串是否以指定的字符串结尾:hasSuffix
NSString *str1 = @"Jackhhhh";
BOOL include = [str1 hasSuffix:@"hhh"];
NSLog(@"是否以hhh结尾:%d",include); //是为1,否为0
(7)在主串中搜索字串,从前往后:rangeOfString
NSString *str1 = @"Jackhhhh";
NSRange range = [str1 rangeOfString:@"ack"];
//返回值是1个NSRange类型的结构体变量.
typedef struct _NSRange {
NSUInteger location; //代表子串在主串出现的下标.
NSUInteger length; //代表子串在主串中匹配的长度.
} NSRange;
NSLog(@"子串在主串中匹配的长度:%ld", range.length); //3
NSLog(@"子串在主串中首次出现的下标:%ld", range.location); //1
如果没有找到,则lengh为0,location为NSUInteger的最大值,也就是NSNotFound
从后往前:NSBackwardsSearch
如果需要从后往前查寻,则需要传入第二个参数:options:NSBackwardsSearch
NSString *str1 = @"Jackhhhjjjhhh";
NSRange range = [str1 rangeOfString:@"hhh" options:NSBackwardsSearch];
NSLog(@"子串在主串中匹配的长度:%ld", range.length); //3
NSLog(@"子串在主串中首次出现的下标:%ld", range.location); //10
(8)字符串的截取
从哪个位置开始截取到最后:- (NSString *)substringFromIndex:(NSUInteger)from
从开始截取到哪个位置:- (NSString *)substringToIndex:(NSUInteger)to
截取一个范围内的字符串:- (NSString *)substringWithRange:(NSRange)range;
NSString *str1 = @"Jackhhhjjjhhh";
NSString *res1 = [str1 substringFromIndex:2];
NSString *res2 = [str1 substringToIndex:8];
NSString *res3 = [str1 substringWithRange:NSMakeRange(2,5)];
NSLog(@"从索引2截取到尾部:%@",res1); //ckhhhjjjhhh
NSLog(@"从开头截取到索引8:%@",res2); //Jackhhhj
NSLog(@"从2开始,截取5个字符:%@",res3); //ckhhh
NSMakeRange创建一个range结构体:第一个值location,第二个值length
(9)字符串的替换:stringByReplacingOccurrencesOfString
NSString *str1 = @"Jackhhhjjjhhh";
NSString *res1 = [str1 stringByReplacingOccurrencesOfString:@"Jack" withString:@"Trip"];
NSLog(@"替换后的字符串:%@",res1); //Triphhhjjjhhh
将原有字符替换为空,就是删除;
(10)字符串类型转换
转换注意. 从头开始转换,能转换多少就是多少. 到遇到不能转换的时候就停止转换.
@property (readonly) double doubleValue;
@property (readonly) float floatValue;
@property (readonly) int intValue;
@property (readonly) NSInteger integerValue
@property (readonly) long long longLongValue
@property (readonly) BOOL boolValue
转换为double
NSString *str1 = @"123123.413asdaf124g";
double db1 = str1.doubleValue;
printf("转换后的:%lf",db1); //123123.413000
转换为int
NSString *str1 = @"123123.413asdaf124g";
int db1 = str1.intValue;
printf("转换后的:%d",db1); //123123
(11)去掉字符串前后空格
仅首尾,无法去除中间
NSString *str1 = @" 123123.413asdaf124g ";
NSString *res = [str1 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSLog(@"去除首尾空格后的字符串:%@",res); //123123.413asdaf124g
去掉字符串首尾小写字母:lowercaseLetterCharacterSet
NSString *str1 = @"abcabc I love U abcabc";
NSString *res = [str1 stringByTrimmingCharactersInSet:[NSCharacterSet lowercaseLetterCharacterSet]];
NSLog(@"去除小写字母后的字符串:%@",res); // I love U
去掉字符串前后的大写字母:uppercaseLetterCharacterSet
NSString *str1 = @"ABC I love U ABC";
NSString *res = [str1 stringByTrimmingCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
NSLog(@"去除大写字母后的字符串:%@",res); // I love U
去掉字符串前后指定的字符串:characterSetWithCharactersInString
NSString * str = @"(_@ I love U @_)";
//- (NSString *)stringByTrimmingCharactersInSet:(NSCharacterSet *)set;
//characterSetWithCharactersInString 去掉字符串前后特定字符(设置字符串内包含的字符)
NSString * newStr =
[str stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"@/:();()¥「」"、[]{}#%-*+=_\|~<>$€^•'@#$%^&*()_+'""]];
NSLog(@"处 理 前:[%@]",str);
NSLog(@"删除字符串前后特定字母处理后:[%@]",newStr);
将字符串转换为大写/小写字符串
字符串全大写:uppercaseString
字符串全小写:lowercaseString
每个单词首字母大写:capitalizedString
【知识点】%p和%@
NSString(@"%p",str);
NSString(@"%@",str);
%p:打印出的是指针变量的值;
%@:打印出的是指针变量的对象;
(13)匿名对象
普通实例化对象:
Person *p1 = [Person new];
那么p1就是对象的名称,
我们后续调用类中属性和方法,也是使用p1来执行的;
如:
p1->_name= @"小明";
p1->_age=18;
[p1 saiHi];
那么匿名对象,顾名思义,就是没有名字,实例化完成后直接用;
[Person new] ->_name = @"小橘"; //第一个对象
[Person new]->_age = 20; //第二个对象
[[Person new] sayHi]; //第三个对象
与p1这种有名字的对象不同,p1为_name _age赋值,以及调用sayHi方法,都是操作的p1这一个对象
但匿名对象只能使用一次,即,每次new都是一个新的对象,因此[[Person new] sayHi]执行完并不能得到赋值后的值,而还是默认值;
因此,匿名对象的使用场景是:这个对象创建之后只用一次
(14)属性的封装
方法的getter和setter
一般情况下,需要修改类的属性或读取时,都需要用getter和setter,而不是用@public
(详细例子待完善)
(15)static
用来修饰局部变量,将这个属性变为静态变量,当方法执行完毕后,变量不会被回收;
如:学生的学号自增长:
+ (instancetype)studentWithName:(NSString *)name andAge:(int)age{
static int num = 1;
Student *s1 = [Student new];
s1->_number = num++;
s1->_name = name;
s1->_age = age;
return s1;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *s1= [Student studentWithName:@"小李" andAge:19]; //number:1
Student *s2 = [Student studentWithName:@"小红" andAge:19];//number:2
}
return 0;
}
因为在num中使用了static修饰符。所以在每次创建student对象时,number并没有每次都从1开始,而是保存上一个状态;也就是会将这个局部变量存储在常量区,当方法执行完毕之后不会回收,下次再执行这个方法的时候,直接使用;
(16)self
self可用在对象方法和类方法中;
用在对象方法中:
1、在方法中声明了和类属性同名的变量,但需要访问类属性时,使用self,self表示当前对象;
(属性要求以下划线开头,但局部变量没有要求,因此尽量避免同名属性出现;)
2、在方法中调用同一对象的其他方法时;
3、在对象方法中,不能调用当前类的类方法;
用在类方法中:
1、类方法中的self表示当前这个类;
2、在类方法中,可以调用当前类的其他类方法,但不能调用当前类的对象方法以及不能访问类的属性;
+ (instancetype)studentWithName:(NSString *)name andAge:(int)age{
static int num = 1;
Student *s1 = [self student]; // 在类方法中创建对象,写法1
Student *s1 = [Student new]; // 写法2
Student *s1 = [Studeng student]; //写法3
[s1 setName:name];
[s1 setAge:age];
[s1 setNumber:num];
return s1;
}
(17)super关键字
1、可以用在类方法和对象方法中,不能用来访问属性;
2、在对象方法中,如果子类想在自己的对象方法中调用父类的对象方法,可以使用super关键字;但同时,因为是继承,父类的就是自己的,所以也可以使用self关键字;
3、在类方法中,子类也可以使用super调用父类的类方法,同样的,继承的就是自己的,也可以使用self,同样类方法可以直接使用父类名或子类名调用;
4、super特指从父类继承过来的方法,增加可读性。
Father.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Father : NSObject
@property (nonatomic, strong) NSString * name;
@property (nonatomic, strong) NSNumber * age;
-(void)eat;
+(void)smile;
@end
NS_ASSUME_NONNULL_END
Father.m
#import "Father.h"
@implementation Father
-(void)eat{
NSLog(@"I am %@, I am %@ yeas old, I can eat",self.name, self.age);
}
+(void)smile{
NSLog(@"I can Smile");
}
@end
Son.h
#import "Father.h"
NS_ASSUME_NONNULL_BEGIN
@interface Son : Father
-(void)learn;
+(void)Shout;
@end
NS_ASSUME_NONNULL_END
Son.m
#import "Son.h"
@implementation Son
- (void)learn{
[self eat]; //调用父类对象方法,写法一
[super eat]; //调用父类对象方法,写法二
NSLog(@"我会学习");
}
+ (void)Shout{
[super smile]; //调用父类类方法,写法一
[self smile]; //调用父类类方法,写法二
[Father smile]; //调用父类类方法,写法三
[Son smile]; //调用父类类方法,写法四
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Son.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//调用父类对象方法
Son *s1 = [Son new];
[s1 setName:@"小明"];
[s1 setAge:@20];
[s1 learn];
//调用父类对象类方法
[Son Shout];
}
return 0;
}
(18)属性访问修饰符
修饰符的类型
@private
私有的,被@private修饰的属性只能在本类的内部访问。
@protected
受保护的,被@protected修饰的属性只能在本类和本类的子类中访问.
****@package
被@package修饰的属性 可以在当前框架中访问。(了解即可)
@public
公共的,被@public修饰的属性可以在任意的地方访问。
默认
如果部位属性指定修饰符,则默认是@protected;
继承
如果父类有私有属性,并在一个方法中使用了私有属性,那么子类可以通过调用父类的方法间接访问父类的私有属性;
修饰符的作用域
从写访问修饰符的地方开始往下,直到遇到另外1个访问修饰符或者结束大括弧为止。中间的所有的属性都属于上面的访问修饰符。
使用建议
1、@public:无论什么时候都不要暴露给外界;
2、@private:如果属性只想在本类中使用,不想在子类中使用;
3、@protected:如果你希望属性只在本类和本类的子类中使用;
推荐使用默认的@protected。
(19)私有属性和私有方法
私有属性:
使用@private修饰的属性就是私有属性,虽然外界不能访问,但外界还是可以看到;
若要实现外界无法看到且无法访问这个属性,则可以不声明属性,直接写在实现中;
@implementation Student{
NSString *_name;
}
私有方法:
方法只写实现,不写声明,就是私有方法,只能在本类中调用;
(20)里氏替换原则(LSP)
定义
子类可以替换父类的位置,且程序功能不受影响;
如:子类Student继承父类Person,虽然*p1需要一个Person类,但是我们返回了一个Student类,这样是允许的;因为子类本身本身继承于父类,拥有父类的所有属性和方法,所以替换后,不影响程序的功能;
Person *p1 = [Student new];
表现形式
父类指针指向子类对象
作用
1、1个指针中不仅可以存储本类对象的地址还可以存储子类对象的地址。Person *p1 = [Student new]
2、 如果1个指针的类型是NSObject类型的,那么这个指针中可以存储任意OC对象的地址,因为NSObject是所有类的基类。NSObject *obj = [任意对象 new]
3、如果1个数组的元素的类型是1个OC指针类型的,那么这个数组中不仅可以存储本类对象还可以存储子类对象。
Person *p1[3];
p1[0] = [Person new];
p1[1] = [Student new];
4、如果1个数组的元素是NSObject指针类型,那么意味着任意的OC对象都可以存储到这个数组之中。
NSObject *obj[3];
obj[0] = [Person new];
obj[1] = [Student new];
obj[3] = @"jack";
5、如果1个方法的参数是1个对象,那么我们在为这个参数传值的时候,可以传该对象和子类对象;
#God.m
#import "God.h"
@implementation God
-(void)killWithPerson:(Person *)p1{
NSLog(@"凡人,%@",[p1 name]);
}
@end
God *god = [God new];
Person *p1 = [Person new];
[p1 setName:@"小明"];
Student *s1 = [Student new];
[s1 setName:@"小李"];
[god killWithPerson:p1]; //既能传本类
[god killWithPerson:s1]; //又能传子类
6、如果一个父类指针指向一个子类对象时,这个父类指针只能调用子类对象的父类成员,不能调子类独有的方法
大致就是:因为需要的是父类对象,虽然传子类对象也能说的通,因为子类是继承父类的,拥有父类的成员,但是本质上需要的还是父类对象,既然是父类对象,那么只能调父类自己的方法;(回归本质了属于是)