Objective-C中的作用域详解

我君哥说过“基础的知识再讨论也不为过”,想必很多人都深有体会。

在此就转一篇基础的文章,由于讲的比较详细,我就稍做整理,标注下重点,然后写点自己的理解(pending)。

原文地址:http://www.itivy.com/iphone/archive/2011/12/21/iphone-objective-c-scope.html


Objective-C中,可以使用几种方式影响程序中变量的作用域。可以改变实例变量以及定义在函数外部或内部的普通变量的作用域。在下面的讨论中,我们使用术语模块(module)来引用包含在一个源文件中任何数目的方法或者函数定义。

控制实例变量作用域的指令

目前,你知道在Objective-C中,实例变量的作用域只限于为该类定义的实例方法。因此,任何实例方法都能直接通过变量名来访问该类的实例变量,而无需特别的操作。

你还知道,Objective-C的实例变量可通过子类进行继承。继承类的实例变量同样可以通过变量名在该子类定义的方法中直接访问。同样,这也无需执行其他特别的操作。

在接口部分声明实例变量时,可以把以下三个指令放在实例变量之前,以便更精确地控制其作用域,它们是:

@protected — 这个指令后面的实例变量可被该类及任何子类中定义的方法直接访问。这是默认的情况。

@private — 这个指令后面的实例变量可被定义在该类的方法直接访问,但是不能被子类中定义的方法直接访问。

@public — 这个指令后面的实例变量可被该类中定义的方法直接访问,也可被其他类或模块中定义的方法直接访问。

@package — 对于64位图像,可以在实现该类的图像的任何地方访问这个实例变量。

如果要定义一个名为Printer的类,它包含两个私有实例变量pageCount和tonerLevel,并且只有Printer类中的方法才能直接访问它们,那么可以如下使用接口部分:

1
2
3
4
5
6
7
8
9
10
@interface Printer: NSObject
{
@ private
int pageCount;
int tonerLevel;
@ protected
// other instance variables
}
...
@end
由于这两个实例变量均为私有的,所以任何从Printer派生子类的人都无法访问它们。

这些特殊的指令和“开关”一样,所有出现在这些指令之后的变量(直到标志着变量的声明结束的右花括号为止)都有指定作用域,除非使用另一个指令。在前面的例子中,@protected指令确保它后面和}符号之前的实例变量可以被Printer的类方法访问,也可以被子类访问。

@public指令使得其他方法或函数可以通过使用指针运算符(一>)访问实例变量。将实例变量声明为公共的并不是良好的编程习惯,因为这违背了数据封装的思想(即,类隐藏它的实例变量)。

外部变量

如果在Objective-C程序的开始处(所有方法、类定义和函数定义之外)编写以下语句:

int gMoveNumber = 0;

那么这个模块中的任何位置都可以引用这个变量的值。在这种情况下,我们说gMoveNumber被定义为全局变量。为了向阅读程序的人说明变量的作用域,按照惯例,用小写的g作为全局变量的首字母。

实际上,在Objective-C中,这样的定义使得其他文件也可以访问变量gMoveNumber的值。确切地说,前面语句不仅将gMoveNumber定义为全局变量,而且将其定义为外部全局变量。

外部变量是可被其他任何方法或函数访问和更改其值的变量。在需要访问外部变量的模块中,变量声明和普通方式一样,只是需要在声明前加上关键字extern。这就告知系统,要访问其他文件中定义的全局变量。下面这个例子说明如何将gMoveNumber声明为外部变量:

extern int gMoveNumber;

现在,包含前面这个声明的模块就可以访问和改变gMoveNumber的值。同样,通过在文件中使用类似的extern声明,其他模块也可以访问gMoveNumber的值。

使用外部变量时,必须遵循下面这条重要原则:变量必须定义在源文件中的某个位置。这是通过在所有方法和函数外部声明变量,并且前面不加关键字extern,如下所示:

int gMoveNumber;

这里,前面显示过,可以为这个变量指派初始值。

定义外部变量的第二种方式是在所有函数之外声明变量,在声明前面加上关键字extern,同时显式地为变量指派初始值,如下所示:

extern int gMoveNumber = 0;

然而,这并不是首选的方法。编译器将给出警告消息:提示你已将变量声明为extern的,并同时为变量赋值。这是因为使用关键字extern的表明这条语句是变量的声明而不是定义。记住,声明不会引起分配变量的存储空间,而定义会引起变量存储空间的分配。因此,前面的例子强行将声明当作定义处理(通过指派初始值),所以违背了这个规则。

处理外部变量时,变量可以在许多地方声明为extern,但是只能定义一次。

通过观察一个小程序例子来说明外部变量的用法。假设我们定义了一个名为Foo的类,并将以下代码键入一个名为main.m的文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import “Foo.h”
int gGlobalVar = 5;
int main ( int argc, char *argc[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Foo *myFoo = [[Foo alloc] init];
NSLog (@”%i “, gGlobalVar);
[myFoo setgGlobalVar: 100]
NSLog (@”%i”, gGlobalVar);
[myFoo release];
[pool drain];
return 0;
}
在前面的程序中,gGlobalVar定义为全局变量,因此任何方法〔或函数)只要正确地使用extern声明,都可以访问它。假设Foo方法setgGlobalVar:如下所示:

1
2
3
4
5
-( void ) setgGlobalVar: ( int ) val
{
extern int gGlobalVar;
gGlobalVar = val;
}
该程序将在终端生成以下结果:

1
2
5
100
这就证明了方法setgGlobalVar:可以访问和改变外部变量gGlobalVar的值。

如果有很多方法需要访问gGlobalVar的值,只在文件的开始进行一次extern声明将比较简便。但是如果只有一个或少数几个方法要访问这个变量,就应该在其中每个方法中单独进行extern声明。这样将使程序的组织结构更清晰,并且使实际使用变量的不同函数单独使用这个变量。注意,如果变量定义在包含访问该变量的文件中。那么不需要单独的extern声明。

静态变量

前面所示的例子与数据封装原则以及良好的面向对象编程技术相违背。然而,可能需要下面这种变量:它们的值在经过不同的方法调用时是共享的。虽然在Foo类中将gGlobalVar定义为实例变量似乎也不太合理,但是,更好的方法可能是通过将访问限制在类中定义的setter和getter方法中,将实例变量“隐藏”在Foo类中。

现在,你知道在方法之外定义的变量不仅是全局变量而且是外部变量。然而,在很多情况下你想要将变量定义为全局变量但不是外部变量。换句话说,希望定义的全局变量只在特定模块(文件)中是全局的。这种变量在下面的情况中很有意义:除了特定类中的方法,再没有其他方法需要访问这个特定变量。要做到这一点,可以在包含这个特定类实现的文件中将该变量定义为static。

如果语句

static int gGlobalVar = 0;

声明在任何方法(或函数)之外,那么在该文件中,所有位于这条语句之后的方法或函数都可以访问gGlobalVar的值,而其他文件中的方法和函数则不行。

你会想起类方法不能访问实例变量(你可能考虑为什么又是这样)。然而,可能希望类的方法可以设定和访问一些变量。简单的例子是类的分配器方法,它要记录类已经分配空间的对象数目。实现这项任务的方式是在类的实现代码文件中设定静态变量。由于这个变量不是实例变量,所以分配器方法可以直接访问它。类的用户不用知道这个变量。因为它是定义在实现文件中的静态变量,作用域只是文件内部。因此,用户不能直接访问该变量,也就没有违背数据封装的概念。如果需要从类之外访问该变量,则可以编写一个方法来获取该变量的值。

下面的代码【1】对Fraction的类定义进行了扩充,增加了两个新方法。allocF类方法分配一个新的Fraction对象,同时记录分配了多少Fraction, count方法则返回这个数的值。注意,后者也是类方法。也可以作为实例方法实现,然而,与向类的特定实例发送消息相比,询问类已经分配了多少实例更有意义。

下面是在头文件Fraction.h中添加的这两个新的类方法声明:

1
2
+(Fraction *) allocF;
+( int ) count;
你可能注意到,继承来的alloc方法并没有被重载;相反,你定义了自己的分配器方法。这个方法就可以利用继承来的alloc方法。下面是需要在实现文件Fraction.m中加人的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation Fraction
+(Fraction *) allocF
{
extern int gCounter;
++gCounter;
return [Fraction alloc];
}
+( int ) count
{
extern int gCounter;
return gCounter;
}
// other methods from Fraction class go here
...
@end
count声明为静态使得定义在执行文件中的方法可以访问它,但是该文件之外都不可以访问。a11ocF方法仅仅递增gCounter变量,然后使用alloc方法创建一个新的Fraction,并返回结果;gCounter方法只是返回计数器的值,这样就隔离了来自用户的直接访问。

回忆一下,因为gCounter变量定义在该文件中,因此不需要在这两个方法中使用extern声明。声明只是为了让阅读该方法的人明白:他访问的变量是定义在该方法之外。变量名加g前缀也是出于同样的目的。因此,大多数程序员一般不使用extern声明。

代码【1】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import “Fraction.h”
int main ( int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Fraction *a, *b, *c;
NSLog (@”Fractions allocated: %i”, [Fraction count]);
a = [[Fraction allocF] init]
b = [[Fract on allocF] init]
c = [[Fraction allocF] init];
NSLog (@”Fractions allocated: %i”, [Fraction count]);
[a release];
[b release];
[c release];
[pool drain];
return 0;
}
结果输出:

1
2
Fractions allocated: 0
Fractions allocated: 3
程序开始运行时,gCounter的值会自动置为0(你应该还记得,如果要将类作为整体进行任何特殊初始化,例如,将其他静态变量的值设置为一些非零值,可以重载继承类的initialize方法)。使用a11ocF方法分配三个Fraction实例之后,count方法检索counter变量的值,它被正确地设置为3。如果要重置计数器或将其设为特定的值,可以在类中添加一个setter方法。然而,在这个程序中并不需要这么做。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页