写在前面:
本来介绍这三个关键字的文章网上随便搜索一下都很多了,为什么要我这里还要写这一篇呢?
一来网络内容多过于零散,收集不便
二来通过写文章来理清思路加深自己对这三个关键字使用的理解
三来也让和我遇到同样问题的朋友们也可以作参考
情景重现:
今天在使用重写init方法的时候出现问题了,重写的init方法如下:
//重写init方法为一个圆赋半径值
- (instancetype)initWith:(double)r{
if(self){
r = _r;
}
return self;
}
在重写init方法之后 ,在main函数里通过如下方法实现初始化
My_Point *point = [[Circle alloc]initWith:5];
NSLog(@"area = %f",[point area]);
area的方法如下(#define PI 3.1415926)
- (double)area{
return PI * _r * _r;
}
正确的结果应该是78.539815 可是我的结果是0.000000
可见半径的值并没有被正确的传递到,然后思考究竟是什么原因:
解决思路:
即然没有被赋值,问题肯定出在这个初始化的函数上。至于是哪里出的问题,我暂时并不知道。于是乎某度:
查到的文章说重写没有起作用可能是方法名写错了,那就看我的方法名:
会不会是initWith:这里的问题 ? 一般的都是写成initWithCircleR这样。于是我改代码如下,相应主函数那里也更改过
//重写init方法为一个圆赋半径值
- (instancetype)initWithCircleR:(double)r{
if(self){
_r = r;
}
return self;
}
结果依然如故。再查也没有什么有用的信息,自己去摸索:
会不会是initWithCircleR:(double)r里面r这里的问题?
于是这里也改成circleR,更改代码如下(相应主函数已改):
//重写init方法为一个圆赋半径值
- (instancetype)initWithCircleR:(double)circleR{
if(self){
r = circleR;
}
return self;
}
这里就不行了,提示r未定义。我反复的去看了一下,确认r已经在.h文件里面定义过了,定义代码如下:
@interface Circle : My_Point
{
double _r;
}
@property double r;
那为什么会提示没有定义呢?加上self试试
- (instancetype)initWithCircleR:(double)circleR{
if(self){
self.r = circleR;
}
return self;
}
这时倒是不报错了,可问题依然存在,结果为0.000000
有些纠结啦,问题究竟出在哪里?
查资料的时候注意到了重写init方法会有下面这一句:
self = [super init];
而在我的重写方法里并没有!于是加上
- (instancetype)initWithCircleR:(double)circleR{
self = [super init];
if(self){
self.r = circleR;
}
return self;
}
问题解决,正确输出!原因就是没有初始化父类init方法
再回头看老师的视频,看到老师的代码是这样写的
- (instancetype)initWithR:(double)r{
self = [super init];//刚开始这里漏写
if(self){
_r = r;//注意这里
}
return self;
}
所以说我在找问题的思路上就有问题,刚开始总是找到一些无关痛痒的地方去尝试。(initWith:)这里其实是一个重写的方法名,只要init+首字母大写的方法名,OC就默认是重写init方法,不管您是initWith还是initWithCircleR都是一样的!
再说到后面跟的参数名r的问题。刚开始是考虑到会不会是这个参数名r和定义的r成员变量名冲突的原因导致的问题,才会想到要去更改参数名,事实证明我的想法是错误的,老师的代码里写的r并没有影响到成员变量,同样输出正确的结果。只不过如果可以应该尽量避免这样写,容易混淆!
当然 ,结果是出来了,可是我依然想知道为什么我用的self.r 和老师的_r = r 输出的结果是一样的?这就涉及到关键字self的使用问题:
有一篇参考文章写的很清晰,贴出来作参考
对重写初始化方法时self = [super init]的写法解释
在这个重写的init函数里,self究竟是一个什么样的角色?
通过上面的文章里面的解释,我知道了self代表的就是Circle。(self 始终代表的是当前方法的调用者)
这也就解释的通为什么通过self.r也可以给半径正确的赋值的原因了,r是在Circle类里定义的。
至于老师的写法 _r = r;(划重点)
这里的“=”前面的r代表的是我后来更改的circleR,其实就是个参数名字。main函数里在调用重写的初始化方法时传递的参数值5,就是保存在r(circleR)里面,即通过赋值后 r = 5;
My_Point *point = [[Circle alloc]initWith:5];
然后这里涉及到了_r 的用法,这里为什么和_r来作为接收参数传递过来的值呢?
我们定义的不是@property int r么?那用的时候不应该是用r来接收么?可明显我用r来接收的时候系统提示未定义。明明在.h文件里我是有定义过的,为什么提示未定义?
进而引出@property 和 @synthesize的用法
参考资料:
为什么变量前要加下划线才有用?
https://blog.csdn.net/sd19871122/article/details/44410933
文章里写的也很详细,我这里再说一下:
其实在变量的声明里面,我们可以不用既在interface{}里面声明,又在@property里面声明,只需要其中一个就可以。区别在于使用@property之后,系统在编译的时候会自动为我们加上getter和setter方法,(篇幅问题:get,set方法的扩展就不说了)。说白了,就是一个变量保护的问题。OC在设计的时候为了系统的稳定,默认在interface里面声明的变量是私有的@private,而私有变量只能由其类的访问(不重写set,get方法的前提下),这就是为什么重写initWithCircleR方法里面如果不加self直接使用r会提示未定义的原因。下面有一篇文章详解变量的属性问题:
OC的私有、保护、公有和KVC
https://blog.csdn.net/rbyyyblog/article/details/52120317
我们即使在同一个类里的成员方法也不可以直接访问在interface定义的成员变量——只能由set和get方法访问。
而为什么用_r就可以访问到呢?因为@synthesize的原因:
按照oc 的官方命名约定,为了避免变量泄漏,实例(成员)变量名一般都建议使用下划线前缀表示法,写个代码大家就都清楚了:
比如声明变量
//.h文件
@property int r;
实现set,get方法(在编译时系统根据关键字@synthesize自动添加)
//.m文件
@synthesize r = _r ;
//这句如果我们不写,系统会自动为我们加上。
在我的.m文件里,我并没有写@synthesize这句,于是系统为我们自动加上并且声明了一个在.m里面使用的实例(成员)变量名_r 。synthesize就相当于搭了一座桥,通过_r,我们可以实现用get,set方法对成员变量的访问与赋值(_r = r(circleR))。
文章到这里结束,菜鸟一只,大家有什么想交流的可以评论。如果您非常有耐心的看到这里了,不嫌麻烦可以给小的点个赞,无比感激。谢谢!