首页 Objective-C 从 C++ 到 Objective-C(3):类和对象

从 C++ 到 Objective-C(3):类和对象

5 1.9K

既然是面向对象语言,类和对象显然是应该优先考虑的内容。鉴于本系列已经假定你已经熟悉 C++ 语言,自然就不会去解释类和对象的含义。我们直接从 Objecti-C 和 C++ 的区别开始说起。

Objetive-C 使用的是严格的对象模型,相比之下,C++ 的对象模型则更为松散。例如,在 Objective-C 中,所有的类都是对象,并且可以被动态管理:也就是说,你可以在运行时增加新的类,根据类的名字实例化一个类,以及调用类的方法。这比 C++ 的 RTTI 更加强大,而后者只不过是为一个“static”的语言增加的一点点功能而已。C++ 的 RTTI 在很多情况下是不被推荐使用的,因为它过于依赖编译器的实现,牺牲了跨平台的能力。

根类,id类型,nilNil的值

任何一个面向对象的语言都要管理很多类。同 Java 类似,Objective-C 有一个根类,所有的类都应该继承自这个根类(值得注意的是,在 Java 中,你声明一个类而不去显式指定它继承的父类,那么这个类就是Object类的直接子类;然而,在 Objective-C 中,单根类的子类必须被显式地说明);而 C++ 并没有这么一个类。Cocoa 中,这个根类就是NSObject,它提供了很多运行时所必须的能力,例如内存分配等等。另外需要说明一点,单根类并不是 Objective-C 语言规范要求的,它只不过是根据面向对象理论实现的。因此,所有 Java 虚拟机的实现,这个单根类都是Object,但是在 Objective-C 中,这就是与类库相关的了:在 Cocoa 中,这个单根类是NSObject,而在 gcc 的实现里则是Object

严格说来,每一个类都应该NSObject的子类(相比之下,Java 应该说,每一个类都必须Object的子类),因此使用NSObject *类型应该可以指到所有类对象的指针。但是,实际上我们使用的是id类型。这个类型更加简短,更重要的是,id类型是动态类型检查的,相比来说,NSObject *则是静态类型检查。Objective-C 里面没有泛型,那么,我们就可以使用id很方便的实现类似泛型的机制了。在 Objective-C 里面,指向空的指针应该声明为nil,不能是NULL。这两者虽然很相似但并不可以互换。一个普通的 C 指针可以指向NULL,但是 Objective-C 的类指针必须指向nil。正如前文所说,Objective-C 里面,类也是对象(元类 Meta-Class 的对象)。nil所对应的类就是Nil

类声明

属性和方法

在 Objective-C 里面,属性 attributes 被称为实例数据 instance data,成员函数 member functions 被称为方法 methods。如果没有特殊说明,在后续文章中,这两组术语都会被混用,大家见谅。

C++

class Foo
{
    double x;
    public:
    int f(int x);
    float g(int x, int y);
};

int Foo::f(int x) {...}

float Foo::g(int x, int y) {...}

Objective-C

@interface Foo : NSObject
{
    double x;
}

-(int)   f:(int)x;
-(float) g:(int)x :(int)y;

@end

@implementation Foo

-(int)   f:(int)x {...}
-(float) g:(int)x :(int)y {...}

@end

在 C++ 中,属性和成员函数都在类的花括号块中被声明。方法的实现类似于 C 语言,只不过需要有作用于指示符(Foo::)来说明这个函数属于哪个类。

Objective-C 中,属性和方法必须分开声明。属性在花括号中声明,方法要跟在下面。它们的实现要在@implementation块中。

这是与 C++ 的主要不同。在 Objective-C 中,有些方法可以不被暴露在接口中,例如 private 的。而 C++ 中,即便是 private 函数,也能够在头文件中被看到。简单来说,这种分开式的声明可以避免 private 函数污染头文件。

实例方法以减号 - 开头,而 static 方法以 + 开头。注意,这并不是 UML 中的 private 和 public 的区别!参数的类型要在小括号中,参数之间使用冒号 : 分隔。

Objective-C 中,类声明的末尾不需要使用分号 ;。同时注意,Objective-C 的类声明关键字是@interface,而不是@class@class关键字只用于前向声明。最后,如果类里面没有任何数据,那么花括号可以被省略。

前向声明

为避免循环引用,C 语言有一个前向声明的机制,即仅仅告诉存在性,而不理会具体实现。C++ 使用 class 关键字实现前向声明。在 Objective-C 中则是使用@class关键字;另外,还可以使用@protocol关键字来声明一个协议(我们会在后面说到这个概念,类似于 Java 里面的 interface)。

C++

//In file Foo.h

#ifndef __FOO_H__
#define __FOO_H__

class Bar; //forward declaration

class Foo
{
    Bar* bar;
public:
    void useBar(void);
};

#endif

//In file Foo.cpp

#include "Foo.h"
#include "Bar.h"

void Foo::useBar(void)
{
    ...
}

Objective-C

//In file Foo.h

@class Bar; //forward declaration

@interface Foo : NSObject
{
    Bar* bar;
}

-(void) useBar;

@end

//In file Foo.m

#import "Foo.h"
#import "Bar.h"

@implementation Foo

-(void) useBar
{
    ...
}

@end

private,protected 和 public

访问可见性是面向对象语言的一个很重要的概念。它规定了在源代码级别哪些是可见的。可见性保证了类的封装性。

C++

class Foo
{
public:
    int x;
    int apple();
protected:
    int y;
    int pear();
private:
    int z;
    int banana();
};

Objective-C

@interface Foo : NSObject
{
@public
    int x;
@protected
    int y;
@private
    int z;
}

-(int) apple;
-(int) pear;
-(int) banana;

@end

在 C++ 中,属性和方法可以是privateprotectedpublic的。默认是private

在 Objective-C 中,只有成员数据可以是privateprotectedpublic的,默认是protected。方法只能是public的。然而,我们可以在@implementation块中实现一些方法,而不在@interface中声明;或者是使用分类机制(class categories)。这样做虽然不能阻止方法被调用,但是减少了暴露。不经过声明实现一些方法是 Objective-C 的一种特殊属性,有着特殊的目的。我们会在后面进行说明。

Objective-C 中的继承只能是public的,不可以是privateprotected继承。这一点,Objective-C 更像 Java 而不是 C++。

static 属性

Objective-C 中不允许声明 static 属性。但是,我们有一些变通的方法:在实现文件中使用全局变量(也可以添加static关键字来控制可见性,类似 C 语言)。这样,类就可以通过方法访问到,而这样的全局变量的初始化可以在类的 initialize 方法中完成。

5 评论

Rex 2011年9月25日 - 23:19

“每一个类都应该是 NSObject 的子类”这一句我不解,不是说NSObject是Cocoa的类吗?这个语言强制要求类必须有个根类却没有定义一个universial的根类?那么NSObject的根类又是什么呢?

回复
DevBean 2011年9月26日 - 17:38

重新查阅一下资料:为了使用和管理的方便,Cocoa 给我们定义了一个 NSObject,可以作为 Root Class 使用。但是,即使在 Cocoa 中,还有一个类不是继承 NSObject 的,就是 NSProxy。由这一点可以看出,语言没有要求必须是单根的。但是,Apple 推荐用户定义的类继承 NSObject,因为可以利用继承自 NSObject 的一系列函数与 Object-C Runtime 进行交互,从而获得内存分配、管理等的能力。除非你重写了 NSObject 里面实现的那些和 Object-C Runtime 交互的消息,比如 alloc 之类的,所以如果不是十分特殊的要求,从 NSObject 及其子类继承是正确的选择。详情见:http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSObject.html

回复
Rex 2011年9月26日 - 23:04

我赞同你的说法,这个NSObject提供了很多强大的功能,对于内存管理等很多事情提供了方便的支持。只是觉得那句话的措辞有点过了,尤其那句“严格来说”,呵呵。不过这不是什么大问题。你文章译的很仔细,排版也很用心,赞一个。

回复
icegull 2012年5月4日 - 14:52

经查阅,代码有误:
@interface Foo : NSObject
{
@public:
int x;
@protected:
int y;
@private:
int z;
}

正确是:
@interface Foo : NSObject
{
@public
int x;
@protected
int y;
@private
int z;
}

回复
DevBean 2012年5月6日 - 17:21

谢谢指出!已经修改过来。

回复

回复 Rex 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2