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-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.