语法之继承
来源:互联网 发布:王者荣耀点卷充值软件 编辑:程序博客网 时间:2024/05/21 09:56
转自:http://sarin.iteye.com/blog/1768779
继承是面向对象的一个核心概念。在Objective-C的继承体系中,位于最顶层的根类是NSObject,类比Java中的java.lang.Object类,我们定义的所有类都是它的子类。子类也叫扩展类或派生类。
我们之前使用的分数类Fraction就是NSObject类的派生类。继承使得子类可以从父类中获得一些属性和已有方法。要注意的是如果子类中要直接使用父类继承过来的实例变量,那么必须将变量声明在接口部分中,而在实现部分声明的变量,子类无法继承使用。在实现部分声明和synthesize的实例变量都是私有的,子类不能直接访问,需要提供设置值和取值方法才可以访问这些变量。
看下面这个例子,我们来具体看看继承的实现:
#import <Foundation/Foundation.h>@interface ClassA:NSObject{ int n;}-(void) initVar;@end@implementation ClassA-(void) initVar{ n=406;}@end@interface ClassB:ClassA-(void) printVar;@end@implementation ClassB-(void) printVar{ NSLog(@"n=%i",n);}@endint main(int argc, const char * argv[]){ @autoreleasepool{ ClassB *clsB=[ClassB new]; [clsB initVar]; [clsB printVar]; } return 0;}
编译运行后,我们得到如下结果:
下面来解释这个程序,该程序中我们定义了两个类ClassA和ClassB,ClassA的父类是NSObject,这是Objective-C中的根类,ClassB继承自ClassA,那么ClassB也间接继承了NSObject类。定义ClassA的接口时,我们定义了一个变量n,那么子类ClassB才能访问到它,ClassA中定义了方法initVar,而ClassB中定义了方法printVar。在执行主函数时,首先创建了ClassB对象,使用initVar来初始化参数,initVar是ClassA中的方法,此时使用ClassB类的引用指针来调用就体现了继承的观点。然后再执行printVar方法,我们就看到了结果。
这里我们使用new来创建对象,是我们用它来代替alloc和init方法,这两个方法我们没有定义却直接使用了,说明它是从根类NSObject中继承而来的。之前代码中的示例也一直使用到了继承。
我们将代码修改如下:
#import <Foundation/Foundation.h>@interface ClassA:NSObject{ int n;}-(void) initVar;-(void) setVar:(int) m;-(void) print;@end@implementation ClassA-(void) initVar{ n=406;}-(void) setVar:(int) m{ n=m;}-(void) print{ NSLog(@"n=%i",n);}@end@interface ClassB:ClassA-(void) printVar;@end@implementation ClassB-(void) printVar{ NSLog(@"n=%i",n);}@endint main(int argc, const char * argv[]){ @autoreleasepool{ ClassA *clsA=[[ClassA alloc] init]; ClassB *clsB=[ClassB new]; [clsB initVar]; [clsB printVar]; [clsA setVar:10]; [clsA print]; [clsB printVar]; } return 0;}
再次编译运行,我们得到如下输出:
这里我们在主函数中创建了对象ClassA,并且为A提供了设置值和打印方法。那么程序中间我们重新设置了ClassA的属性n,然后再打印值来看。那么这个结果告诉我们一个事实:类的每个实例都有自己的实例变量,即便这些变量是继承来的,那么对于父类中的变量修改,子类中的值无影响。ClassB和ClassA拥有完全不同的实例变量。
下面我们继续来深入理解继承,首先设计一个矩形类Rectangle,代码如下:
#import <Foundation/Foundation.h>@interface Rectangle : NSObject@property int width,height;-(int) area;-(int) perimeter;-(void) setWidth:(int) w andHeight:(int) h;@end
实现文件如下:
#import "Rectangle.h"@implementation Rectangle@synthesize width, height;-(int) area{ return width*height;}-(int) perimeter{ return (width+height)*2;}-(void) setWidth:(int)w andHeight:(int)h{ width=w; height=h;}@end
主函数如下:
#import "Rectangle.h"int main(int argc, const char * argv[]){ @autoreleasepool { Rectangle *rect=[Rectangle new]; [rect setWidth:10 andHeight:23]; NSLog(@"Rectangle: width=%i, height=%i",rect.width,rect.height); NSLog(@"Area = %i, Perimeter = %i",rect.area,rect.perimeter); } return 0;}
编译运行,得到如下结果:
这个矩形类的定义很简单,定义了长和宽两个变量,定义了设置长和宽的方法,求面积方法和求周长方法。直接运行即可得到结果并不复杂。如果我们需要一个正方形类,该如何设计呢?正方形是矩形的一个特殊情况,就是长宽相等,那么可以将正方形类视作矩形类的子类,那么我们创建这个类,并编写如下代码:
#import "Rectangle.h"@interface Square : Rectangle-(void) setSide:(int) s;-(int) side;@end
编写正方形类Square的实现:
#import "Square.h"@implementation Square-(void) setSide:(int)s{ [self setWidth:s andHeight:s];}-(int) side{ return self.width;}@end
编写测试的主函数:
#import "Square.h"int main(int argc, const char * argv[]){ @autoreleasepool { Square *square = [Square new]; [square setSide:10]; NSLog(@"Square: s=%i",[square side]); NSLog(@"Area = %i, Perimeter = %i",square.area,square.perimeter); } return 0;}
编译运行,我们得到如下结果:
这里我们定义的Square类继承了Rectangle类,并定义了setSide和side方法用于设置边长和返回边长,那么在实现方法中。setSide方法使用了self关键字调用了父类的setWidth addHeight方法,这也是继承的体现。那么side 方法返回边长即可,注意这里的self.width其实是[self width],父类中我们使用了@synthesize指令,那么对私有属性width就提供了访问方法,而方法名和属性名同名,但是要清楚这里我们是调用的方法而不是属性,这点理解是很重要的。
之前定义了矩形类Rectangle,那么我们如果要在桌面上生成这样一个矩形,就需要定位了。为了简便,我们定义桌面的左下角为直角坐标系(笛卡尔坐标系)的原点,横向向右为X轴正向,竖向向上为Y轴正向。那么我们只要确定了矩形的左下角坐标就可以得到矩形的位置了。此时我们就要引入坐标的概念,那么设计XYPoint类,代码如下:
#import <Foundation/Foundation.h>@interface XYPoint : NSObject@property int x,y;-(void) setX:(int)xVal andY:(int) yVal;@end
XYPoint.h文件定义了坐标类XYPoint的接口信息,这里面我们使用整数作为坐标,暂时不考虑小数坐标点。那么提供一个方法来设置坐标点,其实现代码为:
#import "XYPoint.h"@implementation XYPoint@synthesize x,y;-(void) setX:(int)xVal andY:(int)yVal{ x=xVal; y=yVal;}@end
就是给属性x和y进行赋值,没有什么可多说的。因为我们要为矩形设置原点坐标(矩形左下角坐标),那么就需要对矩形类Rectangle进行修改,代码如下:
#import <Foundation/Foundation.h>@class XYPoint;@interface Rectangle : NSObject@property int width,height;-(int) area;-(int) perimeter;-(void) setWidth:(int) w andHeight:(int) h;-(XYPoint *) origin;-(void) setOrigin: (XYPoint *) point;@end
这是类的接口文件,这里面我们使用了@class指令来指定XYPoint类,@class指令可以为我们指定要使用的类,而不用使用import语句,因为这里我们只需要引入XYPoint的定义而已。如果要引用类的实现部分,那么必须使用import语句,@class就不足以提供所需内容了。同时矩形类加入了两个方法,一个是设置原点origin坐标,一个是获取原点坐标,那么矩形类的实现就修改如下:
#import "Rectangle.h"@implementation Rectangle{ XYPoint *origin;}@synthesize width, height;-(int) area{ return width*height;}-(int) perimeter{ return (width+height)*2;}-(void) setWidth:(int)w andHeight:(int)h{ width=w; height=h;}-(XYPoint *) origin{ return origin;}-(void) setOrigin:(XYPoint *)point{ origin=point;}@end
我们定义私有属性origin来表示坐标原点,提供了设置方法和获取方法,这就没什么可多说的了,最后来看看主函数,该如何使用它们:
#import "Rectangle.h"#import "XYPoint.h"int main(int argc, const char * argv[]){ @autoreleasepool { Rectangle *rect=[Rectangle new]; XYPoint *point=[XYPoint new]; [point setX:10 andY:23]; [rect setWidth:10 andHeight:23]; rect.origin=point; NSLog(@"Rectangle: width=%i, height=%i",rect.width,rect.height); NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); NSLog(@"Area = %i, Perimeter=%i",rect.area,rect.perimeter); } return 0;}
主函数中需要引入两个头文件,因为使用到了它们。创建一个矩形变量和一个坐标变量,对它们赋值后,将坐标原点设置给矩形对象,那么此时矩形对象就拥有了坐标原点,之后我们打印出它们的值,编译运行后得到如下结果:
我们修改一下主函数,代码如下:
#import "Rectangle.h"#import "XYPoint.h"int main(int argc, const char * argv[]){ @autoreleasepool { Rectangle *rect=[Rectangle new]; XYPoint *point=[XYPoint new]; [point setX:10 andY:23]; [rect setWidth:32 andHeight:36]; rect.origin=point; NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); [point setX:23 andY:10]; NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); } return 0;}
这里只是对原点进行了二次赋值,那么编译运行后,我们得到如下结果:
为什么会得到这样的结果?我们并没有显式的再次设置矩形的原点,只是对原点对象重新赋值后,矩形的原点也发生了相应的变化。我们来仔细看一下代码,调用setOrigin方法时,point作为参数传递给该方法,这个值是指针对象,指向了XYPoint对象的内存地址。我们使用rect.origin=point将地址赋值给矩形的原点指针上。因为这样赋值的特性,矩形中的原点和point指向的同一内存空间,那么我们修改了point的值,矩形的origin当然也会跟着改变。那么为了避免这个问题,我们修改setOrigin方法的实现,代码如下:
-(void) setOrigin:(XYPoint *) point{ if(!origin){ origin=[[XYPoint alloc] init]; }origin.x=point.x;origin.y=point.y;}
但是我们却得到了如下错误:
是因为在Rectangle.h中我们使用@class指令来标识XYPoint,而现在需要XYPoint的细节,那么就需要修改头文件,将@class指令改为#import即可。之后修改主函数如下:
#import "Rectangle.h"#import "XYPoint.h"int main(int argc, const char * argv[]){ @autoreleasepool { Rectangle *rect=[Rectangle new]; XYPoint *point=[XYPoint new]; [point setX:10 andY:23]; [rect setWidth:32 andHeight:36]; [rect setOrigin:point]; NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); [point setX:23 andY:10]; NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); } return 0;}
编译运行,得到如下结果:
这样就很合理了,原点就属于矩形自己的,称为它的一个属性了,再次修改坐标点不会对已有原点产生影响。但是问题又产生了,修改主函数如下:
#import "Rectangle.h"#import "XYPoint.h"int main(int argc, const char * argv[]){ @autoreleasepool { Rectangle *rect=[Rectangle new]; XYPoint *point=[XYPoint new]; [point setX:10 andY:23]; [rect setWidth:32 andHeight:36]; [rect setOrigin:point]; NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); [point setX:23 andY:10]; NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); XYPoint *origin=rect.origin; origin.x=32; origin.y=36; NSLog(@"Origin at (%i, %i)",rect.origin.x,rect.origin.y); } return 0;}
如果我们在这里又定义一个对象来获取矩形的原点,然后对其重新赋值,那么我们得到如下结果:
这是因为我们使用origin方法返回时直接返回矩形内的原点引用,那么对这个引用的修改必然导致了上述的结果。出于这种原因,我们要修改origin方法,使其返回一个对象的副本,从而使得对其的修改不影响原有值:
-(XYPoint *) origin{ XYPoint *point=[XYPoint new]; point.x=origin.x; point.y=origin.y; return point;}
注意这里返回时重新创建了一个对象,对于这种开销是否必要,还要根据实际情况来定。
在继承中,不能删除和减少方法,但可以通过覆盖来实现对方法的更改,还是前面的示例,定义ClassA和ClassB,代码如下:
#import <Foundation/Foundation.h>@interface ClassA : NSObject{ int x;}-(void) initVar;@end
类A接口中我们只给出变量定义(为了子类可以使用)和初始化方法,其实现代码如下:
#import "ClassA.h"@implementation ClassA-(void) initVar{ x=100;}@end
这里我们就是实现initVar方法对变量x进行了简单的赋值。那么来看下ClassB的定义:
#import "ClassA.h"@interface ClassB : ClassA-(void) initVar;-(void) printVar;@end
它继承自ClassA,并且比类A多了打印变量的方法,其实现代码如下:
#import "ClassB.h"@implementation ClassB-(void) initVar{ x=200;}-(void) printVar{ NSLog(@"x = %i",x);}@end
代码也很简单,就是对x变量的初始化和打印,那这里也是方法覆盖的体现,那来看测试代码:
#import "ClassB.h"int main(int argc, const char * argv[]){ @autoreleasepool { ClassB *clsB=[ClassB new]; [clsB initVar]; [clsB printVar]; } return 0;}
编译运行,即可得到如下结果:
那么可以看到这里我们创建了类B,并且调用类B的实现代码对变量进行赋值和打印。从而实现了方法覆盖。如果我们将测试代码改写如下:
#import "ClassB.h"int main(int argc, const char * argv[]){ @autoreleasepool { ClassA *clsA=[ClassA new]; ClassB *clsB=[ClassB new]; [clsA initVar]; [clsA printVar]; [clsB initVar]; [clsB printVar]; } return 0;}
显然这里类A是没有printVar方法的,那么会得到如下错误:
因此我们需要修改ClassA的代码,加入printVar方法即可。我们分别创建了类A和类B的对象,它们使用各自的initVar方法后就会初始化自己的x变量,之后再使用各自的printVar方法来打印x的值。clsA和clsB按照各自所属的类选择相应的方法,这就是Objective-C中面向对象的基础。
那么如果我们将printVar方法从ClassB中删除,会是怎样的效果?因为ClassB继承自ClassA,如果ClassA中也未定义printVar方法,显然这里会出现错误。但如果ClassA中定义了printVar方法,那么ClassB就会继承这个方法。运行测试代码,也会打印出200这个值。
继承中还有抽象类的概念,如果一个类的创建只是为了更好的创建子类,那么这个类可以叫做抽象类。这样的类中可以定义实例变量和方法,但是不希望任何人从该类来创建实例,比如NSObject。在这里,只要理解抽象类的含义就可以了。
- 语法之继承
- python语法之继承
- C++语法之继承
- Java基础语法之继承
- Kotlin语法基础之继承
- Objective-C语法之继承、封装、多态
- python语法学习面向对象之继承
- python语法学习面向对象之继承
- Python基础语法之——继承
- C#语法之base继承的使用-QQ聊天记录
- OC基础语法之封装继承多态
- Python语法第10讲:面向对象之继承
- C++(继承语法&&继承方式)
- 继承的语法
- 继承(inheritance)语法
- C++继承语法
- 黑马-OC语法-继承
- 多继承语法
- 文件改动时间排序
- mvn命令集合
- Oracle 中count(1) 和count(*) 的区别
- openstack nova常用命令
- Fengoffice installation with fatal error "Class 'DB' not found"
- 语法之继承
- How The Computer Works
- 【Thinkphp教程】URL路由功能解析
- 通过vb修改windows桌面背景
- 【Thinkphp教程】URL重写
- 第2章 理解memcached的内存存储
- javascript replace的使用
- rational_perm破解
- Android4.X数据库的cursor问题