3.3,类的声明
从objecsive-C名字我们就可以得知,这是一个面向对象的语言。面向对象的一个最基础的要素就是类的概念,objecsive-C也不例外。所谓的类的概念,其实是从C语言的结构体发展而来的。我们知道,C语言里面的结构体仅仅有数据的概念,面向对象的语言不仅仅支持数据,还可以在结构体里面封装用于存取结构体数据的方法。结构体的数据和方法结合,我们把整个结构体称为类(Class)。仅仅有了类,是不能执行任何操作的,我们必须把类进行实体化,实体化后的类我们称之为对象(objecs)。从这个角度上来说,我们可以认为类是对象的模版。
如果要使用类,那么和构造体相类似,我们必须声明这个类。
请参照“Cattle.h” 文件:
1 #import
2
3
4 @interface Cattle : NSobjecs {
5 int legsCount;
6 }
7 - (void)saySomething;
8 - (void)setLegsCount:(int) count;
9 @end
如果看过本系列第二章的同学们,第一行应该是一个老面孔了,我们知道我们需要这个东西免费获得苹果公司为我们精心准备的Foundation Framework里面的很多的功能。如果不使用这个东西的话,我们的工作将会很复杂。
同学们请看第4行和第9行的第一个字母,又出现了“@”符号。为什么说又呢,因为我们在第二章的字符串前面也看到过这个东西。字符串前面出现这个符号是因为我们需要和C语言的字符串定义区别开来,我们需要编译器导向。在这里,我要告诉同学们的是,这里的“@”符号的作用还是同样是编译器导向。我们知道Java和C++定义了一个关键字class用于声明一个类,在objecsive-C里面,不存在这样的关键字。在objecsive-C里面,类的定义从@interface开始到@end结束,也就是说,编译器看到了@interface就知道了这是类的定义的开始,看到了@end就知道,类的定义结束了。
我们这里类的名字是“Cattle”,我们使用了空格和@interface分开,通知编译器,我们要声明一个类,名字叫做Cattle。在Cattle的后面,我们有“: NSobjecs”,这是在通知编译器我们的Cattle是从NSobjecs继承而来的,关于继承和NSobjecs,我们将在后面的章节里面详细介绍,关于“: NSobjecs”我们现在可以理解为,通过这样写,我们免费获得了苹果公司为我们精心准备的一系列的类和对象的必备的方法。NSobjecs被称为root class,也就是根类。在Java或者.NET里面,根类是必备的,C++不需要。在Obejctive-C里面原则上,你可以不使用NSobjecs,构筑一个你自己的根类,但是事实上这样做将会有很大工作量,而且这样做没有什么意义,因为苹果为你提供的NSobjecs经过了很长时间的检验。也许有好奇心的同学们想自己构筑根类,不过至少笔者不会有自己去构筑一个根类的欲望。
好的,大家现在来看第5行。我们以前把这个东西叫做变量,我们从现在开始,需要精确的使用objecsive-C的用语了,这是实体变量(instance variables,在有的英文资料里面会简写为iVars)。虽然作为一个Cattle,它有不止一个实体变量,比如说体重等等,但是为了代码简洁,我们在这里声明一个就是牛腿也就是牛股的数目,这个实体变量是int型,表示一个整数,我们当然不希望有4.5个牛腿。
我们来看第6行,第6行的括弧和在第4行最后的括弧用来表示实体变量的定义区间,编译器认为在这两个括弧之间的定义是实体变量的定义。当然,如果你的类没有实体变量,那么这两个括弧之间允许什么都没有。和Java以及C++不一样,objecsive-C要求在括弧里面不能有方法也就是函数的定义,那么objecsive-C里面的方法的定义放在什么地方呢,请看第7行。
第7行的第一个字母是一个减号“-”。这个减号就是告诉编译器,减号后面的方法,是实体方法(instance method)。实体方法的意思就是说,这个方法在类没有被实体化之前,是不能运行的。我们在这里看到的是减号,在有减号的同时也有加号,我们把带加号的方法称为类方法(class method),和实体方法相对应,类方法可以脱离实体而运行。关于类方法,我们将在后面的章节里面讲解。大家也许可以想起来在C++和Java里面同样也有类似的区分,不是么。
在objecsive-C里面方法的返回类型需要用圆括号包住,当编译器看到减号或者加号后面的括号了之后,就会认为这是在声明方法的返回值。你也可以不声明返回值,objecsive-C的编译器会给没有写显式的返回值函数加上一个默认的返回值,它的类型是id,关于id类型我们将在后面讲解,不过笔者不推荐不写返回值的类型。
在第7行我们定义了这个方法的名字是saySomething,当然Cattle说的话我们人类是听不懂的,笔者只是想让它在我们的控制台里面输出一些我们可以看得懂得字符串。方法的声明最后,需要分号来标识,这一点保持了和C没有任何区别。
我们再来看看第8行,第8行和第7行多了“:(int) count”。其中冒号放在方法的后面是用来表示后面是用来定义变量的,同样变量的类型使用括号给包住,如果不写变量的类型的化,编译器同样认为这是一个id类型的。最后的count,就是变量的名字。如果有不只一个变量怎么办?答案就是在第一个变量后面加冒号,然后加园括号包住变量的类型,接着是变量的名字。
好了,我们在这里总结一下,类的定义方法如下:
@interface 类的名字 : 父类的名字 {
实体变量类型 实体变量名字;
}
- (返回值类型)方法名字;
+ (返回值类型)方法名字;
- (返回值类型)方法名字:(变量类型) 变量名字:(变量类型) 变量名字;
@end
...的意思
在本系列入门讲座里面,...表示省略了一些代码的意思。
3.4,类的定义
我们在前一节讲述了类的声明,我们下一步将要看一下类的定义。请同学们打开“Cattle.m”文件:
1 #import "Cattle.h"
2
3
4 @implementation Cattle
5 -(void) saySomething
6 {
7 NSLog(@ "Hello, I am a cattle, I have %d legs.", legsCount);
8 }
9 -(void) setLegsCount:(int) count
10 {
11 legsCount = count;
12 }
13 @end
14
Cattle.m文件的第一行就import了Cattle.h文件,这一点和C的机制是一样的,关于#import的说明请参照第二章。
我们来看第4行和第13行,和头文件里面的@一样,我们这里类的定义也是使用的编译导向。编译器会把从@implementation到@end之间的部分看作是类的定义。@implementation的后面有一个空格,空格的后面是我们的类的名字Cattle,这是在告诉编译器,我们要定义Cattle类了。第4行和第13行之间是我们在头文件里面定义的实体方法或者类方法的定义部分,当然我们的类如果没有任何的实体方法和类方法的话,我们也许要写上@implementation和@end,把中间留为空就可以了。
第5行是我们定义的saySomething的实现,我们可以发现第5行的内容和头文件Cattle.h的第7行是一致的。笔者个人认为在编写实体方法和类方法的定义的时候,为了避免手工输入产生的误差,可以从头文件当中把声明的部分拷贝过来,然后删除掉分号,加上两个花括弧。我们知道地6行到第8行是方法的定义的部分,我们再来看看第7行。第7行和第二章的Hello, World输出有些相似,只不过多了一个%d,还有实体变量legsCount,这个写法和C语言里面的printf是类似的,输出的时候会使用legsCount来替代字符串里面的%d。
第9行的内容和Cattle.h的第8行一致的,这个不需要再解释了。我们来看看第11行,第11行是在说,把参数count的数值赋值给实体变量legsCount。我们可以通过使用setLegsCount方法来控制Cattle对象里面legsCount的数值。
这部分内容的关键点为@implementation和@end,理解了这个东西,其余的就不难理解了。我们来总结一下,类的定义部分的语法:
@implementation 类的名字
-(方法返回值) 方法名字
{
方法定义
}
-(方法返回值) 方法名字:(变量类型) 变量名字
{
方法定义
}
@end