您的位置:首页 > 其它

self、类的继承和派生、实例变量修饰符、多态、类的本质

2015-08-03 20:16 411 查看
一、self关键字
1、self 和 super
OC提供了两个保留字 self 和 super,用于在方法定义中引用执行 该方法的对象。
OC语言中的self,就相当于C++、Java中的this指针
学会使用self,首先要搞清楚属性这一概念,以及理解setter(设置器)和getter(访问器)方法,它到底有什么用?
在程序中为了安全性的考虑,通常会把程序中属性以及一些底层代码封装起来,不让随便调用,但是也不能不和这里的属性交流,所以就会设置一些设置属性和访问属性的方法,从而就引入了getter(访问器)和setter(设置器)。
假如,没有这个方法,外界怎么操作类的内部属性?
假如不提供这两个方法,那么这个属性的值,就不能被外界所改变。
以为类的属性,默认是@protect(受保护类型),属性一般是私有的。

super发送消息可以将方法实现分发其父类,可以重写一个已有的方法来修改或将它添加到现有的方法中,仍然可以将原始的方法纳入到修改后的方法当中。

2、self的应用场景
1)self在对象方法中使用
self在对象方法中使用,指代的是调用当前对象方法
的那个 对象
下面我们通过历程来分析:
程序如下
Person.h声明部分



在这声明部分没有属性,只定义了两个对象方法。

Person.m部分



这部分主要是为了实现 刚定义的两个对象方法
主函数部分



打印输出



现在,假如这个对象person一吃完饭后就想走走我们该如何实现?
分析:
最好的办法是在我们调用方法 eat的时候,顺便就调用方法run
程序代码如下:
Person.h声明部分



Person.m实现部分



主函数部分



打印输出



从打印部分来看程序确实实现我们的要求,但是这是因为在Person.m中的方法eat中重新定义了person1,也就是说这个方法run是用person调用的,这就像我们吃了饭不是我们自己去走而是让别人区走,这是不符合我们的要求的,要解决这个问题就要引入self关键字。

Person.h声明部分



Person.m实现部分



主函数部分



打印输出



但是这也有一个疑问?现在的person 是同一个person?只要确定是同一个person,那么就证明了程序实现了要求。

验证程序如下:
Person.h声明部分



Person.m实现部分



主函数部分



打印输出



总结:
从打印输出来看self 和 person 指向的地址是一样的并且都是<Person:0x100114590>
从而说明:此时的self 就是person!

self在对象方法中使用,指代的是调用当前 对象方法(此方法中包含
self) 的那个 对象
注意!:
1)在自己的方法中调用自己的方法会造成死循环



2)self 不能在main.m中使用

2、self 用在类方法中
如何在类方法中实现上面的问题:让人吃完饭就出去走走?
首先我们看看类方法的基本实现
程序代码如下
Person.h声明



Person.m实现



主函数



打印输出



从打印来看可以实现类方法的调用
问题:如何用类方法来实现 人吃完饭后走走?

Person.h声明



Person.m实现



主函数



打印输出

小结:
1.>此程序也可以实现我们的要求但是,但不清楚是不是同一个“人”?
2.>用%@打印不出类的地址,但是也不会报错
下面我们来探讨是不是“同一个人”?

Person.h



Person.m



主函数



打印输出



小结:
1.>从打印来看 它们指向的是同一个地址,也就是说是“同一个人”在做这些事,这时满足我们的要求的
2.>%p格式才可以打印出类的地址

下面我们看看用self怎么来实现我们的要求?
Person.h



Person.m



主函数



打印输出



小结:
1.>用self 也是可以在类方法eat中直接调用类方法run,这个比较方便

总结:
1.>


可以知道 [person class]返回的实质就是当前类(类对象)
2.>通过对self在对象方法 和 类方法中使用,可以得出总结:self在那里被调用
就表明self指代的是谁,在对象方法中使用时,self代表的是 “该对象”;在类方法中使用时,self就代表的是“该类”

3、self修饰变量

self在对象方法中可以利用self->属性名称 的方法访问成员变量
应用场景:当存在局部变量名和成员变量名同名的时候,局部变量会屏蔽成员变量的作用域

下面我们通过给一个实例变量赋值并且打印实例变量的值来来self的作用
Dog.h



Dog.m



主函数



打印输出



当我们改变一下实例变量的名称,看看会怎样?
Dog.h



Dog.m



主函数



打印输出



小结:
从打印输出来看输出是由问题的应该输出 100才对,那为什么输出是0?
在我们学C语言的时候就遇到过全局变量和局部变量,当全局变量和局部变量同名时,局部变量会屏蔽全局变量,而在我们现在这里历程中其实遇到了同样的问题,在Dog.h中定义的实例变量speed其实就相当于是我们的全局变量,而方法setSpeed中的参数speed就是局部变量,因为setSpeed是一个给实例变量赋值的一个方法,当在这个方法中局部变量speed会屏蔽全局变量speed,当我们给speed赋值为100时,在方法中speed=100,但是当程序执行到方法的“}”时,内存空间会释放,而全局变量的speed值依然是0,所以会出现上面的问题返回值为0

那么如何才能实现?
方法一:就是把全局变量名和局部变量名起名不想同
方法二:就是使用self来修饰变量
下面我们来看看self修饰的变量程序

Dog.h



Dog.m



主函数



打印输出



小结:
1.>self->speed访问的是实例变量
加上self后表示访问的就是类的实例变量
不加self访问的是局部变量

4、self使用总结和注意事项
1)self使用总结
1.>self 谁调用当前方法,self就代表谁
2.>self在对象方法中,self代表当前 对象
3.>self在类方法中,self代表 类
[self 方法名称];调用其它方法(类方法/对象方法)
self可以简单理解为指自己

2)self使用注意
同时有对象方法 和 类方法存在的时候,self不会调错

二、类的继承和派生
1、OC中的继承与派生
交通工具是一个基类(也称为 父类),通常情况下所有交通工具所供同具备的特性是速度与额定载人的数量。
但按照生活常规,我们来继续给交通工具来细分类的时候,我们会分别想到有 汽车类 和 飞机类等等,汽车类 和 飞机类同样具备速度和额定载人数量这样的特性,而这些特性是所有交通工具所共有的,那么当建立汽车类和飞机类的时候我们无需再定义基类已经有的数据成员,而只需要描述汽车类和飞机类所特有的特性即可,飞机类和汽车类的特性是由在交通工具类原有特性基础上增加而来的,那么飞机类和汽车类就是交通工具类的派生类(也称做子类)。以此类推,层层递增,这种子类获得父类特性的概念就是
继承

2、下面通过实例来体会一下 继承



基类的定义
Animal.h



声明了两个方法:eat方法和run方法

Animal.m



派生类的定义

Dog.h



在Dog中声明了一个lookHome 的方法

Dog.m



主函数



打印输出



小结:
从打印出来的信息知道,当我们在dog调用对象eat方法、run方法的时候都可以调用并且打印出来,但是在我们定义Dog.h中并没有声明eat方法和run方法,只有在Animal.h中有声明这两种方法,所以可以得出结论:Dog类继承类父类Animal中的方法。

3、基类和派生类之间的关系
1)一般情况下,基类和派生类之间的关系如下:



注意!!!
1》基类的私有属性能被子类继承,但是不能被使用
2》OC中的继承是单继承:也就是说一个类只能有一个父类,不能继承多个父类
3》继承的要有合理性:
引用《大话西游》里的一句话来描述继承的合理性:“人是人他妈生的,妖是妖他妈生的!”

2)方法的重写
方法的重写,从父类继承的方法,可能这个方法并不适合子类,可以在子类中重写父类的方法

下面通过实例来看看
父类:
Animal.h



Animal.m



子类:
Dog.h



Dog.m



主函数



打印输出



小结:
从打印出来的情况来看 ,虽然体现出来了 子类继承父类的方法特性,但还是有一些缺点:动物和狗分的不是很清楚,像这样的无论是什么动最后打印出来显示的都是动物,所以这有一点小缺陷,为了弥补这个缺陷,我们要在子类的实现中重写父类的对象方法。

通过实例程序来看看

Animal.h



Animal.m



子类:
Dog.h



Dog.m



主函数



打印输出



小结:
1》从这个打印出来的情况来看,我们在子类的实现中重新修改了父类的对象方法
2》程序在执行时的规律:先查找当前类有没有对象方法eat、对象方法run,如果有,就先调用自己的;如果没有就查看父类有没有对象方法eat、对象方法run;如果父类也没有那就去父类的父类中去找,如果有就执行,如果没有再继续找直到找到NSobject,如果有就执行,如果没有就报错。

3)继承的注意事项
1》子类不能定义和父类同名的变量,但是可以继承父类的变量
2》OC类支持单一继承,不支持多类继承

4)继承体系中方法调用的顺序
1》在自己类中找
2》如果没有,去父类中找
3》如果父类中没有,就去父类的父类中找
4》如果父类的父类也没有,就还往上找,直到找到基类(NSObject)
5》如果NSObject都没有就报错

三、实例变量修饰符

1、实例变量的作用域
public:任意程序集
protected:同一类和派生类
private:同一类

例如:
1)@public(公开的)在有对象的前提下,任何地方都可以直接访问
2)@protected(受保护的)只能在当前类和子类的对象方法中访问
3)@private(私有的)只能在当前类的对象方法中才能直接访问
4)@package(框架级别的)作用域介于私有和公开之间,只要处于同一个框架中就可以直接通过变量访问

2、实例变量修饰符对子类的影响

实例程序如下
Animal.h



Animal.m



子类:
Dog.h



Dog.m



主函数



打印输出



小结:
从打印可以知道@public定义的实例变量是全局的子类是可以继承并且使用的

看下面的程序可以发现:



小结:
上面显示的是在在main.m中用对象dog来调用父类中的实例变量,但是不能在main.m中调用
所以:@protected、@private 定义的变量可以被子类继承但是不能使用。

下面我们来看看 @protected
Animal.h



Animal.m

子类:
Dog.h



Dog.m



主函数



打印输出



小结:从打印出来的情况来看,子类内部可以使用父类用protected定义的实例变量但是不能在其他地方用 子类使用

下面看看@private修饰的实例变量



小结:
对已private修饰的实例变量子类中是不能使用的

总结:
1》@public类型的变量,在子类中可以正常的访问和使用
2》@protected类型的变量,在子类中使用,不能在其他类中使用
3》@private类型变量 在子类中不能被访问更不能被使用

子类可以继承父类的所有的实例变量和方法,但是,不是所有的都可以访问!

3)实例变量作用域使用注意事项
1》在@interface ....@end之间声明的成员变量如果不做特别的说明,那么其默认是protected的
2》一个类继承了一个类,那么就拥有了父类的所有成员变量和方法,注意所有的成员变量它都拥有,只是有的它不能直接访问。

3、OC中的私有变量
1)在类的实现中即.m文件中也可以声明成员变量,但是因为在其他文件中通常都是只包含头文件而不会包含实现文件,所以在.m文件中声明的成员变量是@private的。在.m中定义的成员变量不能和它的头文件.h中的成员变量同名,在这期间使用@public等关键字也是徒劳的。
2)在子类Dog的实现类中,能够使用父类的
@public类型的变量
@protected类型的变量
@private类型的变量可以看到,但是不能使用
私有变量既不能看到也不能使用

实例如下,我们在父类的实现文件中定义一个变量m,在对象方法实现中使用

父类:
Animal.h



Animal.m是和以往的不同的



子类:
Dog.h



Dog.m



主函数



打印输出



四、多态

1、什么事多态
什么是多态:多态就是某一类事物的多种形态
程序中的多态:不同的对象以自己的方式响应相同名称方法的能力成为多态

2、多态的条件
多态的条件:有继承关系、有方法重写
父类的声明变量指向子类对象
代码体现:用父类类型的指针指向了 子类对象,这就是多态

3、多态的优点
多态的主要好处就是 简化了编程接口。它容许在类和类之间重用一些习惯性的命名,而不用为每一个新加的函数命名一个新名字。这样编程接口就是一些抽象的行为的集合,从而和实现接口的类的区分开来。
多态也使得代码可以分散在不同的对象中而不用试图在一个函数中考虑到所有可能的对象。这样使得您的代码扩展性和复用性更好一些。当一个新的情景出现时,您无须对现有的代码进行改动,而只需要增加一个新的类和新的同名方法

4、如何实现多态
Animal是父类,子类有Cat 和 Dog,子类分别重写了父类中的eat方法:实例化对象的时候可以用下面的方法:
Animal *animal=nil;
//实例化猫的对象
animal =[Cat new];
[animal eat];

//实例化狗的对象
animal=[Dog new];
[animal eat];

5、多态的原理
动态绑定:
动态类型能使程序直到执行时才确定对象所属类型
动态类型绑定能使程序直到执行时才确定要对对象调用的实际方法
OC不同于传统程序设计语言,它可以再运行时加入新的数据类型和新的程序模块:动态类型识别,动态绑定,动态加载
id类型:通用指针类型,弱类型,编译时不进行类型检查

6、多态的注意点
1)如果存在多态,父类是可以访问子类特有的方法
假设子类Dog 有一个特有的方法 bark
[dog bark];
Animal *an2=[Dog new];
[(Dog *)an2 bark];//把父类的指针,强制类型转换

2)如果不存在多态,父类是不可以访问子类特有的方法的
Animal *an3=[Animal new];
[(Dog *)an3 bark];//错误的,不能强制转换

下面用实例来体会一下 多态:
但是要记住多态的条件:有继承关系、有方法重写、父类的声明变量指向子类对象

我们规定
父类为:Animal
一级子类:Dog、Cat
二级子类BigYellowDog(父类为Dog)

程序代码:
父类:
Animal.h



Animal.m



以级子类:

Cat.h



Cat.m



Dog.h



Dog.m



二级子类:
BigYellowDog.h



BigYellowDog.m



主函数





打印输出





小结:
从打印输出来看,子类都继承了父类的方法。下面我们在此基础上进行验证多态,因为多态的条件之一就是有继承关系。
我们拿父类Animal 和 子类 Dog来做验证
Animal.h



Animal.m



Dog.h



Dog.m其中对run方法做了修改



main.m



打印输出



小结:
此程序满足多态的条件:有继承关系、有对父类方法的重写

下面我们重写编写一下main.m函数



打印输出



小结:
通过这两个实例比较后知道:
dog1,dog2的类型是:Animal类型,并且指向Dog
dog3,dog4的类型是:Animal类型,并且指向Animal
所以,dog1,dog2可以访问子类Dog中的方法而dog3,dog4不可以访问子类Dog中的方法,只能访问父类Animal中的方法

下面我们来看看如果两个类没有继承关系会怎么样?
类Cat 和类BigYellowDog没有继承关系



结论:如果两个类之间没有继承关系,那么就会报错,无法执行,但是如果我们调用的是这两个类共同的父类方法时也是可以执行的
这应该是违法操作:



打印输出



五、类的本质

1、类类型的变量(实例对象)
Person *person=[Person new];
//把类的代码加载到代码区
//产生一个类对象,类型是classisa指向代码区的Person类

//实例化对象三件事
//申请空间初始化返回地址
//对象isa指向类对象

2、类的本质
类的本质其实也是一个对象(类对象)
类对象:
1》类对象在程序运行时一直存在
2》类对象是一种数据结构,存储类的基本信息:类大小,类名称,类的版本以及消息与函数的映射表等
3》类对象所保存的信息在程序编译时确定,在第一次使用该类的时候被加载到内存中
4》类对象代表类,class代表对象,类方法属于类对象
5》如果消息的接受者是类名,则类名代表类对象
6》运行时,所有类的实例都由类对象生成,类对象会把实例的isa的值修改成自己的地址,每个实例的isa都指向该实例的类对象。
7》从类对象里可以知道父类信息,可以响应的方法等
8》类对象只能使用类方法,不能用实例方法
例如:
Person *p=[Person new];
p:实例对象
Person也是一个对象(类对象),是class类型

3、类对象 如何获取
1)通过实例对象获取
Dog *dog=[Dog new];
Dog *d2=[Dog new];

获取 类对象
Class c=[dog class];
Class c2=[d2 class];

2)通过类名来获取 类对象
Class c3=[Dog class];

实例如下
Dog.h



Dog.m



main.m



4、类对象的使用
1)原来使用类名可以做哪些事情?
创建对象
[Person new];
调用类方法
[Person test];//test是类方法

类对象的使用
类对象:c1
1)创建实例对象
c1 *p=[c1 new];//这样的写法是错误的
Person *p=[c1 new];
2)调用类方法
[c1 test];

实例如下
Dog.h



Dog.m



main.m



打印输出



5、类对象的存储细节
1)类对象的存储图



使用SEL选择器后的存储图



6、SEL类型
SEL:全称selector表示方法的存储位置



Person *p=[[Person alloc ] init];
[p test];
寻找方法的过程:
(1)首先把test这个方法名包装成SEL类型的数据
(2)根据SEL数据找到对应的方法地址
(3)根据方法地址调用相应的方法
(4)注意:在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。
关于_cmd:每个方法的内部都有一个_cmd,代表着当前方法

注意:SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去寻找对应的方法地址,找到方法地址后就可以调用方法,这些都是运行时特性,发消息就是发送SEL,然后根据SEL找到地址,调用方法。
SEL s1 =@selector(eat);
通过s1去调用了
[p2 performSelector:s1];
Objetive-C中的Method结构
struct objc_method{
SEL method_name;//方法名
char *method_types;//方法地址
IMP method_imp;//方法地址(IMP)
};
typedefobjc_method Method;

什么是IMP
IMP是“implementation”的缩写,它是objetive-C方法(method)实现代码块的地址,类似函数指针,通过它可以直接访问任意一个方法。免去发送消息的代价。

获取方法的IMP
(IMP)methodForSelector:(SEL)aSelector;
SEL print_sel=NSSelectorFromString(@"print:");//获得SEL IMP IMP=[person methodForSelector:print_sel];//得到IMP imp(person,print_sel,@"*************");//通过IMP直接调用方法
等效调用:[person print_el:@"************"];
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: