13.4 指针

指针允许你高效地表示复杂的数据结构,更改作为参数传递给函数和方法的值,并且更准确而高效地处理数组。在本章的结尾处,我们还会提示在Objective-C语言中,指针对于对象实现的重要性。

第8章“继承”中讲述了Point和Rectangle类,以及如何对同一对象实现多个指引时,介绍了指针的概念。

要了解指针操作方式,首先必须明白间接寻址(indirection)的概念。在日常生活中我们已经习惯了这种说法。比如,假设需要为我的打印机买一个彩色墨盒。在我工作的公司,所有的采购活动都由采购部门负责的。所以,可以打电话给负责采购的Jim,让他为我订购一个新墨盒。Jim将打电话给本地供应商店来订购该墨盒。这样,事实上我获得新墨盒的方式就是间接的,因为我并没有直接从供应商店处订购墨盒。

这种间接方式同样是Objective-C中指针的工作方式。指针提供了间接访问特定数据项值的途径。有理由认为通过采购部门订购墨盒很有道理(比如,不必了解从哪家供应商店订购墨盒),所以也有很好的理由认为有时在Objective-C中使用指针很有道理。

说得足够多了,应该看看指针的工作方式了。假设已经定义了一个名为count的变量,如下所示:


int count=10;


还可以通过以下声明定义一个名为intPtr的变量,它将允许间接访问count的值:


int*intPtr;


星号向Objective-C系统定义变量intPtr是int的指针类型。这表示intPtr在这个程序中用于间接访问一个或多个整型变量的值。

在前面的程序中,学习了如何在scanf调用中使用&运算符。在Objective-C语言中,它是一元运算符,又称为地址运算符,用来得出变量的指针。所以,如果x是特定类型的变量,那么符号&x就是该变量的指针。如果需要,符号&x可以赋值给任何已经声明为与x同类的指针变量。

所以,给出count和intPtr的定义,可以编写如下表达式


intPtr=&count;


设置intPtr和count之间的间接引用。地址运算符的作用是将指向变量count的指针而不是count的值赋给变量intPtr。图13-1描述了intPtr和count之间的关系。方向线说明intPtr并不直接包含count的值,而是包含变量count的指针这一概念。

13.4 指针 - 图1

图 13-1 指向整型数据的指针

要通过指针变量intPtr引用count的内容,可以使用间接寻址运算符,即星号(*)。如果x是int类型,那么语句


x=*intPtr;


会将intPtr间接指向的值赋给变量x。因为之前将intPtr设置为指向count,所以这个语句的作用就是将变量count的数值10赋给变量x。

代码清单13-8结合了之前的语句,演示两个基本的指针运算符:地址运算符(&)和间接寻址运算符(*)。

代码清单13-8


//Program to illustrate pointers

import<Foundation/Foundation.h>

int main(int argc, char*argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

int count=10,x;

int*intPtr;

intPtr=&count;

x=*intPtr;

NSLog(@“count=%i, x=%i”,count, x);

[pool drain];

return 0;

}


代码清单13-8输出


count=10,x=10


变量count和x以常规方式声明为整型变量。在下一行,变量intPtr声明为“int指针”类型。注意这两个声明行可以合并为一行,如下所示:


int count=10,x,*intPtr;


然后,对变量count应用地址运算符,它的作用就是创建该变量的指针。然后程序将该指针赋给变量intPtr。

程序中下一条语句


x=*intPtr;


的执行过程如下:间接运算符告知Objective-C系统创建变量intPtr,它包含指向另一个数据项的指针。然后这个指针用来访问所需的数据项,该数据项的类型是由指针变量的声明指定的。因为在声明该变量时,已告知编译器intPtr指向整数,所以编译器知道表达式intPtr指向的是整型数据值。而且,因为在前面的程序语句中,已经将intPtr设为指向整型变量count的指针,所以count的值可以使用表达式intPtr间接访问。

代码清单13-9演示了指针变量一些有趣的属性。这里用到了指向字符的指针。

代码清单13-9


import<Foundation/Foundation.h>

int main(int argc, char*argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

char c=‘Q’;

char*charPtr=&c;

NSLog(@“c%c”,c,*charPtr);

c=‘/’;

NSLog(@“c%c”,c,*charPtr);

*charPtr=‘(’;

NSLog(@“c%c”,c,*charPtr);

[pool drain];

return 0;

}


代码清单13-9输出


Q Q

//

((


定义了字符变量c并且将其初始化为字符‘Q’。在程序的下一行代码中,变量charPtr定义为“char指针”类型,意思是无论存储在该变量中的是什么值,都应该看作字符的间接引用(即指针)。你将注意到可以使用常规方式给这个变量赋初值。在程序中赋给charPtr的值是指向变量c的指针,它是通过在变量c前面加上地址运算符得到的(注意如果在该语句之后定义c,那么这个初始化语句将产生编译错误,因为必须在表达式中引用变量的值之前声明这个变量)。

变量charPtr的声明和初始值的分配都可以等效地使用如下两个语句表示:


char*charPtr;

charPtr=&c;


(而不是像前一行声明暗示的那样,通过语句


char*charPtr;

*charPtr=&c;


表示)。

始终记住在Objective-C语言中,将指针设置指向一些值之前,指针的值是没有意义的。

第一个NSLog调用仅仅显示变量c的内容以及charPtr所引用的变量内容。因为你将charPtr设为指向变量c,所以显示的就是c的内容,在程序输出的第一行就能得到验证。

该程序的下一行,将字符‘/’赋给字符变量c。因为charPtr仍然指向变量c,所以最后的NSLog语句中显示charPtr的值就正确地显示为c的新值。这是非常重要的概念。除非更改charPtr的值,否则表达式charPtr总是访问c的值。这样,当c的值发生变化时,*charPtr的值也相应地改变。

前面的讨论有助于理解程序中的下一条语句的工作方式。我们提到过,除非更改charPtr,否则表达式*charPtr将总是引用c的值。所以,在以下表达式中


*charPtr=‘(’;


将左括号赋给c。从形式上说是将字符‘(’赋给charPtr指向的变量。你知道这个变量是c,因为在程序的开始将c的指针存入了charPtr。

上述概念是理解指针操作的关键。如果还不是很清楚,再回顾一下这些知识。

13.4.1 指针和结构

你已经看到如何将指针定义为指向基本数据类型(如int和char)。但是指针还可以指向结构。在本章的前面,定义了如下date结构:


struct date

{

int month;

int day;

int year;

};


与使用下面的语句定义struct date类型的变量一样


struct date todaysDate;


也可以定义指向struct date变量的指针变量:


struct date*datePtr;


然后就可以用期望的方式使用刚才定义的变量datePtr。比如,可以使用下面的赋值语句将其设为指向变量todaysDate的指针:


datePtr=&todaysDate;


如上赋值之后,就可以用如下方式通过datePtr间接访问date结构的任何成员:


(*datePtr).day=21;


这个语句的作用是将datePtr指向的date结构中的day成员设置为21。括号是必需的,因为结构成员运算符比间接寻址运算符*的优先级别高。

要测试存储在datePtr指向的date结构中month成员的值,可以使用如下语句:


if((*datePtr).month==12)

……


因为经常用到结构指针,所以该语言中存在着一个特殊的运算符。结构指针运算符->,即短划线和紧跟其后的大于号,它允许将表达式


(*x).y


更清楚地表示为


x->y


所以,前面的if语句可以方便地写为:


if(datePtr->month==12)

……


第一个说明结构的代码清单13-6通过结构指针的概念被重新改写。这个程序被改写成这里的代码清单13-10。

代码清单13-10


//Program to illustrate structure pointers

import<Foundation/Foundation.h>

int main(int argc, char*argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

struct date

{

int month;

int day;

int year;

};

struct date today,*datePtr;

datePtr=&today;

datePtr->month=9;

datePtr->day=25;

datePtr->year=2009;

NSLog(@“Todays date is%i/%i/%.2i.”,

datePtr->month, datePtr->day, datePtr->year%100);

[pool drain];

return 0;

}


代码清单13-10输出


Todays date is 9/25/09.