self关键字
成员变量是定义在类中的变量,它可以被所在的类的方法所调用,而局部变量是定义在方法中的变量,当局部变量的名称与成员变量名称相同的时候,局部变量会覆盖成员变量,导致无法访问成员变量。为了解决这个问题,OC中定义了一个self关键字(类似java的this),它可以访问成员变量,解决局部变量与成员变量名称冲突的问题。
我们将举例来说明,我们先建一个Person类。
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
{
int _age;
}
-(void)setAge:(int)age;
-(int) age;
-(void) test;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
@implementation Person
-(void)setAge:(int)age{
_age=age;
}
-(int) age{
return _age;
}
-(void) test{
int _age=20;//这里是局部变量,覆盖了成员变量,他是一个新的地址,放的是20
NSLog(@"我引用的是局部变量_age,我的年龄值是%d ",_age);
NSLog(@"我引用的是成员变量self->_age,我的年龄值是%d ",self->_age);//这里使用的是成员变量
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person/Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person=[Person new];
[person setAge:10];
[person test];
}
return 0;
}
我们在main函数中,定义了一个Person的实例对象,并将_age的值设为10,我们来看输出的结果,就能看到区别了。
2020-09-28 20:05:40.933867+0800 深入理解面向对对象[926:19684] 我引用的是局部变量_age,我的年龄值是20
2020-09-28 20:05:40.934486+0800 深入理解面向对对象[926:19684] 我引用的是成员变量self->_age,我的年龄值是10
Program ended with exit code: 0
通过上面的结果可以看到引用的是局部变量的输出值是20,引用的是成员变量的值是10。
self调用方法
self关键字不仅可以访问成员变量,还可以调用方法,用法同成员变量差不多,直接在方法里面调用这个语法就行了。语法格式如下:
[self 方法名];
self在调用方法的时候注意避免调用方法自己,如果一个方法中利用self调用方法的本身,则会造成死循环,影响程序的正常运行。
点语法
(一)认识点语法
声明一个Person类:
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
int _age;//默认为@protected
}
- (void)setAge:(int)age;
- (int)age;
@end
Person类的实现:
#import "Person.h"
@implementation Person
- (void)setAge:(int)age
{
_age = age;// 不能写成self.age = newAge,相当与 [self setAge:newAge];
}
- (int)age //get方法
{
return _age;
}
@end
点语法的使用:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
//[person setAge:10];
person.age = 10;//点语法,等效与[person setAge:10];
//这里并不是给person的属性赋值,而是调用person的setAge方法
//int age = [person age];
int age = person.age;//等效与int age = [person age]
NSLog(@"age is %i", age);
[person release];
}
return 0;
}
(二)点语法的作用
OC设计点语法的目的,是为了让其他语言的开发者可以很快的上手OC语言开发,使用点语法,让它和其他面向对象的语言如java很像。
(三)点语法的本质
点语法的本质是方法的调用,而不是访问成员变量,当使用点语法时,编译器会自动展开成相应的方法。切记点语法的本质是转换成相应的set和get方法,如果没有set和get方法,则不能使用点语法。
如:
Stu.age=10;//展开为:[stu setAge:10];
int a=stu.age;//展开为:[stu age];
编译器如何知道是set方法还是get方法?主要是看赋值(可以使用断点调试来查看)。
在OC中访问成员变量只有一种方式即使用-> 如stu->age,这种情况要求在能够访问的权限的前提下。
(四)点语法的使用注意
下面的使用方式是一个死循环:
(1)在set方法中,self.age=age;相当于是[self setAge:age];
(2)在get方法中,return self.age;相当于是[self age];
还有一种get/set的方法是使用@property,具体的用法去看看我写的类的声明、实现和封装。
构造方法
在OC中,有两种实例化对象的方法
类 * 类名=[类 new];第一种方法
类 * 类名=[[类 alloc]init];第二种方法,更直观
上面第一种不过就是第二种写法的一个简写。我们可以从上面的代码中,可以分为两步:第一步就是为对象分配储存空间,第二步是将对象初始化。对象的初始化是通过调用NSObject的init方法实现的,因此,init方法也称为构造方法。OC中的构造方法和 java都差不多,就是语法不同而已。
重写init方法
默认情况下,OC对象的初始值为nil(就是只定义没有为他分配空间的时候),但是,若想在实例化对象的同时,就为对象的某些属性赋值,则需要重写init方法。重写init方法大致可分为四步,具体如下所示。
1.首先通过"[super init]"调用父类的初始化方法。
2.检查父类返回的对象,如果是nil,则初始化不能进行,需要向接收者对象返回nil。
3.在初始化实例对象时,如果它们是其他对象的引用,则在必要时要进行保留。
4.为实例变量设置初始值,并返回self。
为了方便大家的理解,我们将通过举例来说明。我们在上面的person类里进行改造。我们在构造方法里是他的age值默认为1,不再为0,并且不给实例化对象的age赋值。代码如下:
Person.m
#import "Person.h"
@implementation Person
#pragma mark - 重写init方法
//上面是分组导航标记,当类中的方法太多的时候,使用这种方法可以快速定位
-(id)init{
//初始化父类中声明的一些成员变量和属性,返回当前对象,赋值给当前对象
self = [super init];
if(self!=nil){
_age = 1;
}
return self;
}
-(void)setAge:(int)age{
_age=age;
}
-(int) age{
return _age;
}
-(void) test{
int _age=20;//这里是局部变量,覆盖了成员变量,他是一个新的地址,放的是20
NSLog(@"我引用的是局部变量_age,我的年龄值是%d ",_age);
NSLog(@"我引用的是成员变量self->_age,我的年龄值是%d ",self->_age);//这里使用的是成员变量
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person/Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person=[Person new];
NSLog(@"age = %d",person.age);
}
return 0;
}
我们再来看看输出结果:
2020-09-29 14:31:36.969089+0800 深入理解面向对对象[813:14332] age = 1
Program ended with exit code: 0
通过上面的结果可以看到,我们并没有在main函数中实例的person的age赋值,但是输出来的age值是1,而非默认的0;
自定义构造方法
自定义构造方法说白了就是让构造方法可以传递参数,比如上面的init方法没有接收参数,所以age值是写死的,为1。但是我现在不想让他的age值默认为1,而是为10或者20怎么办?这个时候只需要向init方法里扔一个参数就行了。
语法格式如下:
-(id) initWithAge:(int) age;//initWithAge是名字,名字可以随便取,不过最好还是按照见名知其意的方式写名字。但是返回值必须是id类型。
我们还是通过代码来理解,我们再在上面的代码进行一些改造。
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
{
int _age;
}
-(id) initWithAge:(int) age; //initWithAge是名字,名字可以随便取,不过最好还是按照见名知其意的方式写名字。但是返回值必须是id类型。
-(void)setAge:(int)age;
-(int) age;
-(void) test;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
@implementation Person
#pragma mark - 重写init方法
//上面是分组导航标记,当类中的方法太多的时候,使用这种方法可以快速定位
-(id)init{
//初始化父类中声明的一些成员变量和属性,返回当前对象,赋值给当前对象
self = [super init];//可以不写
if(self!=nil){
_age = 1;
}
return self;
}
#pragma mark - 自定义构造方法
-(id) initWithAge:(int) age{
self = [super init];
if(self!=nil){
_age = age;
}
return self;
}
-(void)setAge:(int)age{
_age=age;
}
-(int) age{
return _age;
}
-(void) test{
int _age=20;//这里是局部变量,覆盖了成员变量,他是一个新的地址,放的是20
NSLog(@"我引用的是局部变量_age,我的年龄值是%d ",_age);
NSLog(@"我引用的是成员变量self->_age,我的年龄值是%d ",self->_age);//这里使用的是成员变量
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person/Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person=[Person new];
NSLog(@"person的age = %d",person.age);
Person *person1=[[Person alloc]initWithAge:10];//实例化一个person,并使用自定义构造方法,使其age值为10
NSLog(@"person1的age = %d",person1.age);
Person *person2=[[Person alloc]initWithAge:20];//实例化一个person,并使用自定义构造方法,使其age值为20
NSLog(@"person2的age = %d",person2.age);
}
return 0;
}
我们在main函数中,实例化了三个Person类,person是使用的默认构造方法,其他两个使用的自定义的构造方法。我们看看运行结果
2020-09-29 15:01:22.840029+0800 深入理解面向对对象[927:20835] person的age = 1
2020-09-29 15:01:22.840638+0800 深入理解面向对对象[927:20835] person1的age = 10
2020-09-29 15:01:22.840715+0800 深入理解面向对对象[927:20835] person2的age = 20
Program ended with exit code: 0
description方法
类似java的toString方法。没有重写这个方法的时候直接输出的类的值就是这个类在内存中的地址。我们来看下面的代码。
#import <Foundation/Foundation.h>
#import "Person/Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person=[Person new];
NSLog(@"person的值是 %@",person);
}
return 0;
}
看看运行结果
2020-09-29 15:25:43.143567+0800 深入理解面向对对象[1093:28243] person的值是 <Person: 0x10046cc80>
Program ended with exit code: 0
可以看到输出的是<类名:对象的内存地址>
我们来对person.m重写一下description方法。
#import "Person.h"
@implementation Person
#pragma mark - 重写init方法
//上面是分组导航标记,当类中的方法太多的时候,使用这种方法可以快速定位
-(id)init{
//初始化父类中声明的一些成员变量和属性,返回当前对象,赋值给当前对象
self = [super init];
if(self!=nil){
_age = 1;
}
return self;
}
#pragma mark - 自定义构造方法
-(id) initWithAge:(int) age{
//self = [super init];
if(self!=nil){
_age = age;
}
return self;
}
#pragma mark - 重写description方法
-(NSString *)description{
return [NSString stringWithFormat:@"age = %d",_age];
}
-(void)setAge:(int)age{
_age=age;
}
-(int) age{
return _age;
}
-(void) test{
int _age=20;//这里是局部变量,覆盖了成员变量,他是一个新的地址,放的是20
NSLog(@"我引用的是局部变量_age,我的年龄值是%d ",_age);
NSLog(@"我引用的是成员变量self->_age,我的年龄值是%d ",self->_age);//这里使用的是成员变量
}
@end
我们main函数不改动,继续输出person
#import <Foundation/Foundation.h>
#import "Person/Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person=[Person new];
NSLog(@"person的值是 %@",person);
}
return 0;
}
现在再来看运行结果:
2020-09-29 15:35:06.453063+0800 深入理解面向对对象[1168:31526] person的值是 age = 1
Program ended with exit code: 0
可以看到已经输出我们想要输出的了。