Objective-C入门 Objective-CC#C++CGCC
程序员文章站
2022-07-15 19:03:14
...
Objective-C入门 |
A first look at ObjC(中文版) 转自: http://www.fish888.com/Objective-C-t68418 Objective-C入门(A First Look at Objective-C) 原著:Noah Roberts (jik) 翻译:jeff_yecn 顾名思义,Objective-C就是面向对象的C语言。你可以使用Objective-C编程,但不使用任何非C语言的东西。但那样将是对这语言的浪费。Objective-C使用所有的C语言的内容,同时添加了一些使得它更具面向对象特征的语法。本指南假定你已经知道什么是面向对象编程,同时熟悉C(你不必是这方面的专家,但是我不打算在这里教你应该已经知道的东西)。 我同时必须建议你不要仅仅读这一篇文章,因为它远远不是在Objective-C(ObjC)方面的终极资源。在互联网上你可以找到其它更好、更详细的学习这种语言的资料。在我的网站:members.xoom.com/jik_you上有一些链接,也许你可以从那里开始搜索。 ObjC,和其他面向对象语言一样,是围绕着对象的,所以让我展示一下对象的构成。在ObjC中,对象有两部分。它有一个界面,是对象的外在表现。同时它有一个实现,就是它的内部工作机制。换句话说,就是外界看到的部分和看不到的部分。有时,这样更容易理解。 界面 在ObjC中,界面通常有它自己的头文件(通常是.h),但并不是一定要这样。你可以把界面和实现放在一个文件中,或者把几个界面放在一起,但每个界面是不同的,你必须对此很清楚,否则将会感到非常苦恼。我将只使用在头文件中定义界面的办法。 首先,你必须导入这个对象的超类的界面。你可以使用与“#include”相同语法的预处理标识“#import”。事实上,两者之间的区别只是#import会进行检查以确保文件仅被包含一次。有些人选择使用#include,这完全符合语法,但ObjC设计是使用#import,你也许愿意考虑这一点而保护你的界面文件。 如果你使用gcc(或egcs等)来作为你的编译器,也许你会愿意使用开关:-Wno-import,否则,gcc会无休止地提示你使用import来代替include。 因此,以下是以一个对象为超类的新类的第一行代码,也通常是所有类的开头。 #import /* gcc运行时库的对象 */ 现在,如果你计划引用其他的类,你可以导入他们的头文件,更好的办法是使用@class来声明这些标识是类。界面并不需要知道这个类是什么和怎么做的全部信息,所以引用它的头文件并没有作用,让我们使用@class。 @class String; //简单吧? 顺便说一下,ObjC同时使用“//”和“/* */”来作为注释,这有时会比较方便。 好了,现在我们准备让世界知道想要什么类型的对象,所以让我们声明它。首先我要说明的是我在声明一个新类,在此之前,你应该定义好这个新类的超类。 @interface NewClass : Object //新类在左边,超类在右边。 我并没有漏掉“;”,就是这样写的。 现在我们声明实例变量,不管他们是不是私有的(我碰巧认为私有的东西是不应该被人看见的,实际上也是这样:P)。我在一对“{}”中间写上,就象我们在C的结构中做的那样。 { @public int aValue; @protected //缺省情况下只能被子类所见 int value2; @private //不可见 int mrInvisible; } 做完这些以后,我们要放入method的原型定义。method只能是公共的,虽然如果我愿意的话,我可以演示给你看如果你访问一个私有的method的时候会产生的编译器警告信息。 method既有“类”的(也有些人叫做“工厂”的),也有“实例”的。类method只能由类对象访问,实例method显然由实例访问。例如,我声明了一个保存实例的变量,但在我使用它之前,我必须使用一个类method来产生一个这个变量指向的实例,这样我才可以调用它的实例method。我这样做了以后,我就不能再访问它的类method了。在我释放对象实例之前,我只能访问它的类method。描述一个method时,你根据它是类method还是实例method响应在前面放上“+”或“-”的符号。然后,在“( )”中你填入method返回值的类型,除非你返回的是代表任何对象的通用类型id。在然后,如果你的method有参数的话,你先写一个“:”,跟着是在“( )”中间的参数类型,再接着是变量名。你可以一直写下去,直到以“;”结束。 同时,在第一个参数之后和下一个“:”之前,你还可以放入一些单词,但单词与“:”之间不能有空格。 也许你现在已经糊涂了,让我们看一个类和实例method。 +createWith: (int)value; //足够简单 -(void)say: (String *)what to: (String *)name; //第一个参数是what, // to是method名称的一部分。 在参数表中间使用额外的单词可以使得method念起来更容易理解。就象上面第二个method,念起来象“say 'Hello' to jik”,而不是“say 'Hello', jik”。 可变长度参数表的语法和C语言中是一样的: -(void)doStuff: (id)format,...; 最后,当我们完成全部的定义以后,我们通过下面的语句告诉编译器我们完成了: @end 这里谈的都是关于对象的。你可以使用typedef或其他在C中可以放入头文件的语法继续定义对象。 名字的小规则 method名由除了+/-,类型和参数以外的所有东西组成。例如,method“-(void)say: (String *)what to: (String *)name”的名字在内部是“say:to:”。如果你使用gdb ObjC补丁,这也是用名字引用method的方法。 同时,你应该记住ObjC通常会被处理为C语言并传递到C编译器。过程中名字通常会发生改变,你应该牢记这一点而不要做可能会迷惑编译器的事......就好象: -roll_d: (int)sides; -roll: (int)count d: (int)sides; 在这个例子中,两个method都会被gcc编译器转化为以下的C函数: __i_ClassName_roll_d_() 这个事情发生过在我身上,所以相信我,千万小心。如果我是你的话,我不会在method名的中间使用“_”,在开头使用则没有什么问题。 消息 由于你在实现中将经常使用消息,我们必须首先讨论他们是什么以及在语法上怎么表达。一个消息只是对象method的简单调用。在面向对象编程中,由于它就好象给一个对象发送消息告诉它做某些事,因此称作消息。要表达一个消息,首先是对象的名字,跟着是你要它执行的method以及你要传递的参数,这一切用“[ ]”包围起来。 例如: [object jumpAround]; 或 [object jump: forward]; “object”可以是一个类也可以是类的一个实例。通常类以大写字母开头,而实例则以小写开头。如果“object”是类的话,你只能要求它执行类method,反之亦然。 例如,要建立类“Object”的一个新实例,我们可以使用...: [Object alloc]; “alloc”是为新对象分配内存的类method。但这样并不能使得对象能够使用,因为它的实例变量还没有初始化...要那样做的话,我们调用“init”...因此,我们需要做的全部事情是: id object=[Object alloc]; [object init]; 如果消息的返回值的类型合适的话,你也可以使用它作为另一个消息的一部分。由于“alloc”消息返回id类型,我们可以用这个返回值作为消息发送的对象。由于空返回值不能做任何事情,因此通常返回id会比返回void要好。在你的method执行完成后,最好能够返回指向当前对象自身的变量“self”,除非你不想它作为其他消息的参数或发送对象...如果那样的话,返回void吧。一个使用空返回值的好例子是释放内存的method,不好的例子是分配内存的时候。所以,我们可以使用这个特性来在一行中完成分配内存和初始化的工作: id object=[[Object alloc] init]; 当然,你必须确定init method也返回id,否则我们不能继续使用“object”变量。 消息的执行按照先进先出的顺序。如果你把一个消息作为另一个消息的一部分,所有的内层的消息回在外层的消息执行前执行和返回。最终的返回值为最后一个执行的消息的返回值,内层的消息不会在最后返回。 实现 这是对象完成所有工作的部分。它定义所有对象的method的内部工作机制,准确的说,是所有非继承的部分。实现文件的后缀为“.m”。 首先,你需要导入类自己的和这个对象中将要用到的类的头文件。象通常一样,你可以做额外的define,typedef以及其他你可能会在C源程序开头做的事情。 当你完成所有的准备工作,你告诉编译器你开始定义一个新对象的实现部分。你用与界面定义类似的method来进行,但你不在需要指明超类是什么,除非你没有为这个对象创建一个界面(这是可能做到的)。 当然,如果你愿意的话,你也可以在说明一次超类是什么,没有任何规定说明你不能那样做。你可以用创建界面定义一样的method来做。 @implementation NewClass 在这里你不需要添加实例变量,所以在实现中不需要包括那一部分。我们现在要做的是定义新的method,以及我们需要覆盖的继承下来的method。有关的语法是一种界面中的method声明和C函数的交叉。 以下是一个简短的method定义的例子: -sayHello { ? printf("Hello world! "); ? ? ? return self; } 下面一个例子是有参数使用消息的: -say: (String *)What { ? ? printf("%s ",[what cString]); ? ? ? return self; } 你用与界面定义相同的method结束实现定义。 @end 特殊的method 当你在派生一个类的时候,你要知道有些method的情况有些特殊。在不同的API中,它们的称呼有所不同,但其中相似的已经分类在一起。有些method在多数情况下都不应该被覆盖,有些可以被覆盖,在你这样这样做的时候必须满足一些特定的要求。 你不应该覆盖“alloc”method。这个method做了些特别的事情来为新的实例分配存储空间。它已经被在Object中定义成能够为你需要创建实例的任意类分配正确的存储空间。如果你覆盖它,你的类很可能不能正确工作。 如果你使用函数库使用一种引用计数技术,例如libFoundation使用自动释放池来进行对象清除,你不能覆盖release,autorelease和retain method。这些method使用特别的东西来跟踪对象的引用。改变其中任何一个method会打乱整个*。 init method可以被覆盖,但你必须保证在做其他事情之前调用超类的init method,否则有些变量可能会没有初始化。总是在覆盖的init method的第一行写上[super init];。对你的库函数中使用的dealloc或free method,同样需要调用超类的method,但是是在结尾。而不是method的开头。简单地把“return [super dealloc];”放在最后一行就足以确保完成超类所需要的所有清除工作。 非标准的问题 ObjC有许多的分支。对各种语言的功能和运行时支持没有一个绝对的标准。各种ObjC编译器之间并不兼容。这很大程度上意味着你要定位于一种编译器,因为它们之间的差异是如此的大,以至于你不能使用#ifdef来编写跨编译器代码。 不但运行时库是完全不同,语言也有不同的扩展。比如,ObjC的有些类型如“类别(Catagory)”和“协议(Protocol)”并没有在全部编译器上实现。有些有,有些没有。我肯定这可以更深入的讨论,而这仅仅是问题的表面。 在选择编译器的时候,你必须要理解这一点。有些编译器,象POC,能够跨平台兼容,但缺少语言中的一些部分。有些更完整些,但仍然缺少一些重要的部分,比如,gcc是ObjC的一个比较完整的实现,但仍然缺少对模块卸载的支持(ObjC建议具有在运行时添加和移除一些部分的能力,gcc可以加载他们,但不能移除)。 有些编译器之间存在一些关系。象Stepstone和POC更多地来自于由Brad Cox编写的该语言的最初实现版本。其它的,象gcc,更多地来源于NeXT。 他们之前都是不同的,如果不重写一大部分源代码,并不能在另一个环境下进行编译(假如存在这种可能的话)。记住这一点,下面我将介绍一些不那么具有可移植性的概念。在此之前,我讲的内容包括在ObjC的所有实现中。现在我们要进入不是那么标准的领域。 新数据类型 ObjC有一些我现在应该提一下的新数据类型。其中的有些类型的名称可能会有不同,或者有些没有实现。你要验证一下你的编译器是否支持。 BOOL是布尔类型,可以是YES或NO,NO为0,YES为非0。 STR为char *。 IOD为FILE *。 id是代表任何ObjC对象的通用类型。 SEL类型是代表method的变量。你可以要求对象执行SEL代表的method。你用@selector(methodName)来创建SEL。“methodName”是method名的ObjC表示形式。例如前面提到的“say:to:”。 IMP用C语言指针代表一个method。这用于你需要节省寻找消息的处理程序的时间的情况下,因为IMP是对method的直接链接。通常用于需要高速度的循环过程中。 这些也许对你来说基本够用了......象我前面说过的,这篇文章不应该是关于ObjC的唯一资源。 类别(Catagory) 类别是扩展对象能力的一种方法。你可以在对象中添加新的method,但你不能添加新的实例变量。新的method被视为扩展的类的一部分,子类将继承这些新的δ堋D阋部梢酝ü?唇ㄏ嘤Φ睦啾鹄锤哺抢嗟method。如果你有两个覆盖一个类的method的类别,究竟哪一个覆盖并不确定。 类别的语法与类的语法相似。他们都有界面和实现。当然,也有一些轻微的差别。类别定义的开头几行象这样: @interface ClassName (CatagoryName) @implementation ClassName (CatagoryName) ClassName是你要扩展的类的名称,CatagoryName是类别的识别的名称。类别可以访问所扩展的类的实例变量。 其余的语法是相同的:在界面中声明method,在实现中定义它们,并以@end结束。 你可以用类别建立私有method。把类别的界面放在类的实现文件的实现代码的前面。然后把你需要作为私有mthod的放在这个类别中。创建对象的实现,然后,在这个文件的末尾,加入类别的实现。这样可以对外隐藏这些method,如果在其他地方试图访问这些method,编译器将会给出警告。它不能够在运行时避免被访问,而只能在编译时警告你。在ObjC中没有办法禁止method被访问。 协议(Protocol) 协议,顾名思义,定义一组类之间相互遵守的行为规范。协议没有界面和实现,但他们与界面更加相似。你不需要添加新的实例变量,也不添加新的method或定义任何method的内部实现。协议所需要做的只是定义一套要求所有类都必须包含的method,以遵循这个协议。 协议在你使用ObjC的动态类型机制的时候特别有用。ObjC拥有和Java类似的运行时的动态类型。这是它优于C++的一点,C++只支持“编译时”的动态类型。 比如你要创建一个可以接受任何对象的method。你必须确定这个对象可以响应你要发送的消息,否则你将会发送这个对象不明白的消息给它。这会导致一些问题。如果你使用了协议,你就可以确保它能响应你将要发给它的许多消息,如果你仅是需要发送一个消息,你还有其它方法来检查......这一点我留给你自己去研究。 同时,在上述的情况中,你还可以使协议要求你所期望的参数。我现在会展示给你看怎么完成这些工作。 定义一个新的协议,首先: @protocol ProtocolName 接着是任意数量的method的声明,请使用在界面定义的相同的语法。最后以@end结束。 现在,为了遵循这个协议,你首先声明你要创建的新对象的界面的内容。然后,你必须在这个对象的实现中定义协议中的所有method。 @interface NewObject : Object 上述的语句告诉编译器这是一个叫NewObject的对象,它的超类是Object,并要符合ProtocolName的协议。 要查询一个对象是否满足特定的协议,可以使用“conformsTo"的mthod。 [object conformsTo:@protocol(ProtocolName)]; 这会根据对象是否符合的情况返回YES或NO。 要查询一个参数是否符合特定的协议,可以象如下地声明method: -method:(id)argument; (译注:原文如此,在gcc中似乎并不支持,按照逻辑来说应该为:-method:(id ) argument;) 你也可以返回满足协议的对象: -(id ) method; 结论 好了,以上就是关于ObjC的快速和琐碎的介绍,要学习它,你需要继续阅读其他的信息资源。我的网站有一些链接,除此以外......做一个网站搜索或询问一下。我会给你一个简短的例子,它可以用gcc编译。 Noah Roberts (jik) 原作者授权声明: 我在此授权,可以免费将本文以任何形式或格式进行复制和分发,但这个版权声明必须出现在正文内或复制品内(在复制品内,它必须明显地可以被读者读到)。你可以编辑本文以修复拼写和语法错误。但你不能以任何形式修改它的内容。如果你不同意这个授权协议,你应该联系我来复制本文档。 “I herby grant, free of charge, the right to copy and redistribute this work whole in any form or format, as long as this copyright notice appears intact somewere on or about the copy (it must be obviously available to the reader of the copy, from the copy). You may edit this document to fix spelling or gramatical errors, but you must not modify it's content in any other way. If you object to this licence you must contact me for permission to copy this document.” 最后修订:1999年3月16日 |