4.2 类与对象
现在,我就要创建真正的机器人类型了。
与大多面向对象编程语言的区别在于,在Objective-C中并不使用class关键字来定义类,而是使用两个部分来定义类,包括接口(interface)部分和实现(implementation)部分。
理论上讲,我们可以将类的接口部分和实现部分都放在一个文件中,但我们一般会将接口部分定义在头文件(.h)中,而将实现部分定义在相应的模块文件(.m)中。
■4.2.1 接口部分
接下来的测试工作,我们将继续使用SimpleOC项目;在Xcode中,通过菜单“File”→“New”→“File”,选择OS X下的Source,然后选择Cocoa Class,接下来,需要我们指定类的名称(Class)及其基类(Subclass of),如图4-1所示。
图4-1 创建类
单击“Next”按钮,我们还需要指定代码保存的路径,然后,Xcode会自动为创建以类的名称命名的头文件和代码文件。首先,我们看一看头文件中的接口部分,如下面的代码(CRobot.h文件)。
#ifndef __CRobot_h__ #define __CRobot_h__ #import <Foundation/Foundation.h> @interface CRobot : NSObject -(void) move; @end #endif
我们可以看到,类的接口部分定义在@interface和@end指令之间,而类的名称定义在@interface指令后面,紧跟其后的冒号(:)含义为继承,本例中,我们定义的CRobot类继承自NSObject类。
在CRobot类中,我们声明了一个名为move的实例方法,它没有返回值(使用void关键字声明),稍后,会看到更多关于方法的内容。接下来,我们先来看一看如何在实现部分定义这个方法。
■4.2.2 实现部分
下面的代码,我们将在CRobot.m文件中看到CRobot类成员的具体实现。
#import "CRobot.h" @implementation CRobot -(void) move { NSLog(@"机器人移动"); } @end
在这里,我们可以看到,类的实现部分定义在@implementation和@end指令之间。在@implementation指令后需要类的名称,但不需要再次指定继承哪个类。
在这里,可以看到move方法的实现代码,它的功能很简单,只是显示一条信息。
请注意,在类中的方法并没有使用小括号()来包含参数。实际上,其参数的定义方式与函数有所不同,稍后,我们会看到相关内容。
■4.2.3 创建对象(实例化)
下面的代码,我们演示了如何在main()函数中使用CRobot类。
#import <Foundation/Foundation.h> #import "CRobot.h" int main(int argc, const char *argv[]) { @autoreleasepool { CRobot *robot5 = [[CRobot alloc] init]; [robot5 move]; } return 0; }
代码中,我们需要使用#import指令引用CRobot.h文件。然后,在main()函数中,我们使用如下代码声明了一个CRobot类的实例,即robot5对象。
CRobot *robot5
大家可以看到,对象是被定义为指针类型的,这就是加*的意义。
接下来,我们注意给robot5对象赋值的代码,它实际上完成了对象的实例化过程,这个过程共调用了两个方法,即alloc方法和init方法,也许大家会问,我们并没有定义这两个方法,它们是从哪里来的呢?答案就是,它们是从继承NSObject类而来的,也就是说,这两个方法是定义在NSObject类中的,由于CRobot是NSObject类的子类,所以,我们可以在CRobot类中使用这两个方法。
请注意,并不是基类中所有成员都能被子类访问的,这与类成员的访问级别有关,稍后我会讨论相关主题。
最后,我们调用了robot5对象的move方法,在Objective-C中,类或对象的方法调用,其基本格式如下。
[<类或对象> <方法名和参数>];
调用类或对象中的方法时,需要使用一对方括号[]包含起来,如果你学习过C#或Java等编程语言,可能对这种方法的调用格式有些不适应,不过,用着用着也就习惯了。
■4.2.4 类的成员
前面的示例已经介绍了如何定义一个简单的类和方法,以及如何使用对象及其成员。实际开发中,类成员的定义会比前面的示例复杂得多,我们先来了解一些关于类成员定义和应用的基础知识。
1.属性和方法
定义一个类时,接口中定义的成员也就是类型对外公开的成员,主要包括属性和方法,属性用于定义对象的特性,而方法(任务)则用于定义对象可执行的动作。稍后,我们会详细讨论属性和方法的创建与使用。
2.实例方法和类方法
在前面定义的CRobot类中,我们定义的move属于实例方法,使用减号(-)定义。
-(void) move;
实例方法的特点是,它必须由对象,即类的实例来调用。
另一种方法是类方法,它由类来调用,类方法使用加号(+)定义。
+(void) methodName;
调用类方法时,直接使用类的名称。
[CRobot methodName];
更多关于方法定义的内容稍后讨论。
3.实例变量
在类中,可以定义一些实例变量,这些变量可以在实例方法中调用,我们还可以通过@public、@private、@protected指令指定实例变量的适用范围(称为作用域或访问级别)。
我们可以在类的接口部分或实现部分定义实例变量,此时,应在紧跟接口或实现指令后的一对花括号{}之间,如下面的代码,我们在CRobot类的接口部分定义了counter实例变量。
@interface CRobot : NSObject { int counter; } -(void) move; @end
在接口部分定义的实例变量,其默认使用范围是@protected(受保护的),这些实例变量可以在当前类或其子类的实例方法中使用。
实现部分定义的实例变量,只能用于当前类中的实例方法,相当于@private(私有的)访问级别。如下面的代码。
@implementation CRobot { int counter; } // 其他代码 @end
如果想简单点,还可以直接在接口部分定义全部的实例变量,并指定其访问级别,如下面的代码。
@interface CRobot : NSObject { @private int counter = 0; @protected int x; int y; @public int identity; } -(void)move; @end
其中,counter变量为私有的实例变量,只能在本类中的实例方法中使用。x和y变量定义为受保护的实例变量,可以在本类或其子类的实例方法中使用,而identity变量则定义为公共的,可以由CRobot类型的对象使用→运算符调用,如下面的代码。
CRobot *robot5 = [[CRobot alloc] init]; robot5→identity = 5; NSLog(@"当前ID : %i", robot5→identity);
不过,在类中使用公共的实例变量并不是好的应用方式,如果我们需要使用对象的数据,可以将这个数据定义为属性。
4.访问级别
定义一个类时,有些成员是需要提供给外部代码调用的,而有些成员则只能在类的内部使用,此时,我们就应该考虑成员的访问级别问题。
一般来讲,我们将公共成员(属性和方法)声明在接口部分,然后在实现部分实现它们;而只限于本类或其子类使用的成员则应该定义在类的实现部分。