OBJECTIVE-C入门(2) 类的声明和定义
编译处理指令
既然Objective-C是面向对象的程序语言,所以理应支持可重用的数据和函数的封装体,即类。
类是在结构体的基础上发展的产物,结构体只能处理数据,在结构体之上增加对该数据处理的函数,就构成类的概念。类使程序总能提供对数据专门处理函数的安全调用,使得一系列的机能作为一个子系统供安全且重复的使用。
像结构体一样,类在使用之前必须先声明,但是Objective-C并没有像其他面向对象语言那样提供声明类的专用关键字或者语法,而是用编译处理指令来实现,特征是类声明语句都须以@符号开始。
类声明的编译处理指令以@interface开始,以@end结尾,在这之间代码便是类变量的定义和方法的声明。类的声明和定义比其他语言复杂,这会让刚开始学习Objective-C的人非常困惑(准确的说,其他语言如Java只需定义类而不用声明,而Objective-C需要先声明再定义)。
@interface 类名 : 父类名 {
实例变量定义
...
}
方法声明
@end
这就是Objective-C类声明的语法结构,其中实例变量是供类内部使用的变量,和结构体的成员变量相似,但是实例变量不能从类的外部使用,原则上只能被类内部的方法使用(当然只是原则上)。类可以没有实例变量,这时{}可以省略。
类的方法(注意和类方法的区别)是专属该类的方法,与普通函数的区别是:类的方法可以操作类内部的实例变量。
紧接类名后边的是父类名,也就是说可以指定类的父类,构成继承。继承使类可继承使用父类的机能,而仅定义父类没有的机能。比如父类是抽象的哺乳动物类,如果定义猫类的时候,可以继承哺乳动物这个父类,而在猫类中只定义猫区别与普通哺乳动物类的特有功能即可。
父类可以不指定,这时编译器会为类提供一个缺省的类,即根类。
Objective-C的根类随具体编译器的不同而有所不同,GCC编译器中是Object,Mac OS X的Cocoa编译环境中则是NSObject,除非自己开发根类时可以不指定父类,一般情况下都要采用系统提供的根类作为父类。仅在根类开发时,类的定义可以如下(没有父类):
@interface 类名
{
实例变量声明
...
}
类方法声明
@end
为什么所有的类都必须继承自一个共同的根类,那是因为根类提供了类正常动作所必须的一些基本机能,比如内存的取得和释放,如果没有根类,那么所有的类都要自己去完成这些基本的工作,会使类非常的复杂而不能专注于具体业务(后面还会具体说明)。
类中实例变量的声明和普通C语言的变量声明没有什么区别,但是类方法的声明则差别很大,语法如下:
-(返回值类型) 方法名 : 临时参数列…; |
首先,最左边的减号,代表该方法是类的实例方法,如果是加号则代表该方法为类方法。这里须记住一点:普通定义类的时候,方法前用减号即实例方法即可,类方法是可以不用生成实例而直接调用的方法。实例方法和类方法的区别后面会详细说明。
其次,返回值类型要放在()中,如果函数需要参数的时候,参数的类型也要放到()中,然后紧接参数名,如果有多个参数,则用逗号隔开。
C语言函数的缺省返回值类型是int型,在Objective-C中新追加了一个id型的对象类型作为缺省函数的返回值类型。虽然返回值可以不指定,但一般不建议这么做。如果没有返回值,则要用(void)做明确说明。
@interface Test : Object
- (void)method;
@end
在这个Test类声明中,声明了一个没有返回值,没有参数的方法method,类和方法以及变量的命名规则和C语言一样,以字母或下划线开头。但是习惯上类名的首字母大写,方法名则全部小写。
到这里,类的声明已经做好了,但是方法method只是声明了名称和类型,具体实现的代码怎么写呢?事实上,Objective-C将类的声明和定义完全分开,在类的声明中,只能定义实例变量和方法名及类型,具体的实现要用到@implementation这个编译处理指令中进行。
@implementation 类名
实例方法定义
...
@end
@implementation这个编译指令具体定义@interface中声明的方法,声明过的方法,必须在这里具体定义。
类的实例化
类经过声明和定义,但是还不能直接使用,使用类之前必须分配具体的内存领域且进行适当的初始化。根据类的声明具体分配一块内存,这个过程叫实例化,而具体分配的这块内存,叫做实例或者对象。
C++或者Java中为我们提供好了new运算符,可以自动由类生成实例并完成初始化,而Objective-C实例化竟然也要类自己完成(即需要类给出实例化自己的方法)。实例化需要调查生成对象的大小,申请内存完成复杂的初始化,这些任务对普通用户太难了,不过幸好编译环境为我们提供了根类(Object)来帮助完成这些功能,这也是为什么所有的类都必须继承自根类的原因(否则实例化方法要自己写)。
在根类Object中,定义了实例化根类以及继承自根类的类的类方法alloc,一般的实例方法,必须先有实例才能调用,但是类方法,没有实例也可以使用。所以类方法alloc虽然没有实例,也是可以正常调用的。鉴于alloc是用来生成实例的类方法,所以也常常也被称作工厂方法。
+alloc; |
这是Object中alloc的声明,+开头,说明是一个类方法,缺省返回值为id类型,没有参数。
id型是代表对象的广义类型,什么类型的对象都可以放入id型的对象中。
下面,就要调用alloc类方法,生成实例了,但是怎样写呢?如果熟悉C++,可能会首先想到下面的调用方式:
id obj =类名.alloc(); |
遗憾的是,这种写法是错误的,和大多数面向对象语言的调用方式不同,Objective-C有自己独特的调用方式:
[类名 类方法名:参数序列...] |
如果是调用实例方法的时候,要将类名换成实例名,其中[ ]在Objective-C中被称为消息,这是因为在Objective-C中,从对象调用方法的时候,不是直接调用,而是向对象发送特定的消息,对象接收到消息后,根据消息内容启动相应的方法,这种方式虽然带来了程序的柔软性,但是比C语言的调用方式带来额外的消耗,因为程序执行时要耗费时间检索特定的方法然后启动。
#import <stdio.h>
#import <objc/Object.h>
@interface Test : Object
- (void)method;
@end
@implementation Test
- (void)method {
printf("Kitty on your lap\n");
}
@end
int main() {
id obj = [Test alloc];
[obj method];
return 0;
}
上面的程序是一个Objective-C简单但完整的类实例例子,@interface完成类的声明,继承自根类Object。@implementation完成类的定义,具体定义method的实现方法。
类声明和定义准备好之后,在main函数中,首先定义id型的对象变量obj,然后调用Test类的父类Object的类方法alloc生成实例,最后调用实例obj的实例方法method,实现输出打印。
另外,如果不定义obj变量(或者说Test的实例只在这里使用一次就不用了),也可以像下面的写法:
[[Test alloc] method] |
注:像上面的程序片段一样,虽然对类的声明和定义的具体位置没有要求,但是习惯上把类的声明放到头文件中,类的定义放到和类名同名的.m文件中。