多态:相同的方法名,不同的类
多态能够使来自不同类的对象定义相同名称的方法。
Complex.h接口文件
#import <Foundation/Foundation.h>
@interface Complex : NSObject
@property double real,imaginary;
-(void) print;
-(void) setReal:(double) a andImaginary: (double) b;
-(Complex *) add: (Complex *)f;
@end
Complex.m实现文件#import "Complex.h"
@implementation Complex
@synthesize real,imaginary;
-(void) print
{
NSLog(@" %g+%gi",real,imaginary);
}
-(void) setReal:(double)a andImaginary:(double)b
{
real = a;
imaginary = b;
}
-(Complex *) add:(Complex *)f
{
Complex *result = [[Complex alloc] init];
result.real = real +f.real;
result.imaginary = imaginary + f.imaginary;
return result;
}
@end
Fraction.h的接口文件
#import <Foundation/Foundation.h>
@interface Fraction : NSObject
@property int numerator,denominator;
-(void) print;
-(void) setTo:(double) n over:(double)d ;
-(double) converToNum;
-(Fraction*) add: (Fraction*)f;
-(Fraction*) subtract: (Fraction*) f;
-(Fraction*) multiply: (Fraction*) f;
-(Fraction*) divide: (Fraction*) f;
-(void) reduce;
@end
Fraction.m实现文件#import "Fraction.h"
@implementation Fraction
@synthesize numerator,denominator;
-(void) print
{
if (numerator%denominator==0) {
NSLog(@"%i",numerator/denominator);
}else if ((numerator/denominator<=-1)||(numerator/denominator>=1)){
NSLog(@"%i %i/%i",numerator/denominator,abs(numerator%denominator),abs(denominator));
}
else{
NSLog(@"%i/%i",numerator,denominator);
}
}
-(double) converToNum
{
if(denominator!=0){
return (double) numerator/denominator;
}
else
return NAN;
}
-(void) setTo:(double) n over:(double)d
{
numerator=n;
denominator=d;
}
-(Fraction*) add:(Fraction *)f
{
Fraction* temp = [Fraction new];
temp.numerator = numerator * f.denominator+denominator * f.numerator;
temp.denominator=denominator * f.denominator;
[temp reduce];
return temp;
}
-(Fraction*) subtract:(Fraction *)f
{
Fraction* temp = [Fraction new];
temp.numerator = numerator * f.denominator -denominator * f.numerator;
temp.denominator=denominator * f.denominator;
[temp reduce];
return temp;
}
-(Fraction*) multiply:(Fraction *)f
{
Fraction* temp = [Fraction new];
temp.numerator = numerator * f.numerator;
temp.denominator=denominator * f.denominator;
[temp reduce];
return temp;
}
-(Fraction*) divide: (Fraction*) f
{
Fraction* temp = [Fraction new];
temp.numerator = numerator * f.denominator;
temp.denominator=denominator * f.numerator;
[temp reduce];
return temp;
}
-(void) reduce
{
int u = numerator;
int v = denominator;
int temp;
while (v !=0) {
temp=u%v;
u=v;
v=temp;
}
numerator/=u;
denominator/=u;
}
@end
测试程序:
#import "Fraction.h"
#import "Complex.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
Fraction *fracResult;
Fraction *f1 = [Fraction new];
Fraction *f2 = [Fraction new];
Complex *c1 = [Complex new];
Complex *c2 = [Complex new];
Complex *comResult;
[f1 setTo: 1 over: 10];
[f2 setTo: 2 over: 15];
[c1 setReal:18.0 andImaginary:2.5];
[c2 setReal:-5.0 andImaginary:3.2];
[c1 print];NSLog(@" +");[c2 print];
NSLog(@"-------");
comResult = [c1 add: c2];
[comResult print];
NSLog(@"\n");
[f1 print];NSLog(@" +");[f2 print];
NSLog(@"-------");
fracResult = [f1 add:f2];
[fracResult print];
}
return 0;
}
当执行comResult = [c1 add: c2];
[comResult print];
时,知道第一条消息的接受者c1是一个Complex对象,因此选择定义在Complex类中的add:方法。系统总是携带有关“一个对象属于哪个类”这样的信息。该信息能使系统在运行时做出这些关键性的决定,而不是在编译时。使用不同的类共享相同方法名称的能力称为多态。它可以让你开发一组类,这组类中的每一个类都能响应相同的方法名。每个类的定义都封装了响应特定方法所需的代码。多态还允许添加新类,这些新类可以响应相同的方法。
动态绑定和id类型
动态类型能使程序直到执行时才确定对象所属类。动态绑定则能使程序直到执行时才确定实际调用的对象方法。
例如;
测试程序:
#import "Fraction.h"
#import "Complex.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
id dataValue;
Fraction *f1 = [Fraction new];
Complex *c1 = [Complex new];
[f1 setTo: 2 over: 5];
[c1 setReal:10.0 andImaginary:2.5];
dataValue = c1;
[dataValue print];
dataValue = f1;
[dataValue print];
}
return 0;
}
运行结果:10 + 2.5i
2/5
系统总是跟随对象所属的类,先判断对象所属的类,然后在运行时确定需要动态调用的方法,而不是在编译的时候。
编译时和运行时检查
因为存储在id变量中的对象类型在编译的时候是无法确定的,所以一些事情是需要在运行时才能够确定。
例如:类Dog只有一个run方法,而类Cat只有一个jump方法,那么下面这段代码在编译的时候就是会出错
Dog*dog =[[Dog alloc] init];
[dog jump]; [dog release]; id dog =[[Dog alloc] init]; [dog jump]; [dog release];
因为编译器知道dog是Dog类的一个对象,而当遇到[dog jump]消息的时候,编译器同样知道Dog类是没有jump方法的,所以在编译阶段就会提出警告。但是如果将代码换成下面的样子在编译阶段就不会出错,因为在编译阶段编译器并不知道dog中存储的对象的类型是什么,所以在运行的时候程序就会crash。
以上两种情况就说明了编译时检查和运行时检查机制。也可以看出如果滥用id类型会埋下的潜在隐患。因为id类型可以看做是动态类型,即编译器不会检查对象的类型,而是在运行时动态确定的,如果对象和要执行的方法不匹配,就会出错。而将变量定义为特定类型的对象时,就是静态类型,静态类型是会接受编译器检查的。很显然静态类型可以在编译阶段就指出错误而且提高程序的可读性。
同时,使用动态类型调用方法时,如果在多个类中定义有同名方法,那么这个方法的各个参数类型和返回值类型要一致。
id数据类型与静态类型
虽然说id数据类型可以存储任何类型的对象,但是不要养成滥用这种通用类型的习惯,原因如下:
首先,将一个变量定义为特定类的对象时,使用的是静态类型,在编译的时候就知道这个变量所属的类,这个变量总是存储特定类的对象。使用静态类型时,编译器尽可能的确保变量的用法在程序中始终保持一直,编译器能够通过检查来确定应用于对象的方法是由该类定义的或者由该类继承的,否则就会显示警告。静态类型能够更好的在程序编译阶段就指出错误。并且使用静态类型可以提高程序的可读性。
动态类型的参数和返回类型
如果使用动态类型来调用一个方法,就需要注意:在多个类中实现名称相同的方法,那么每个方法的参数和返回值都必须要符合定义的时候的类型,这样编译器才能为消息表达式生成正确的代码。因为如果参数和返回类型的参数和返回类型是动态的,那么不同类的同名方法用着不同类型的参数,编译的时候编译器可能会不能正确的选取参数的类型。