指针可以高效地表示复杂地数据结构,更改作为参数传递给函数和方法地值,并且更准确地高效地处理数组。指针提供了一种途径,间接地访问特定数据项地的值。
现在有一个count变量:
int count = 10;
还可以声明一个指针名为intPtr,允许间接访问count的值:
int *intPtr;
星号定义变量是int 指针类型。接下来让intPtr指向count:
intPtr = &count;
这样就建立起intPtr和count的间接引用。&为一元运算符,称为地址运算符,用来得到变量的地址(指针)。
要通过指针变量intPtr来引用count的内容,可以使用间接寻址运算符,即星号(*),如果x是int类型:
x = *intPtr;
要记住,将指针设置指向一些值之前,指针的值没有意义。
1.指针和结构
指针除了指向基本数据类型之外,还可以指向结构。
以前定义了一个date结构。如下:
struct date
{
int month;
int day;
int year;
};
struct date todayDate;
struct date *datePtr;
现在可以将datePtr设置为指向todayDate的指针。
datePtr = &todayDate;
可以通过如下语句访问date结构的任何变量:
(*datePtr) . day = 21;这里括号是必须的,因为结构成员点运算符(.)比间接运算符(*)的优先级高。因为经常用到j结构指针,所以在语言中存在一个特殊的运算符——结构指针运算符->,刚才的语句可以等效于datePtr->day = 21;
2.指针,方法和函数
可以按照一般方式将指针作为参数传递给方法或函数,并且可以让函数或者方法返回指针。如下是一段有关指针的代码,可以体会一下:
#import <Foundation/Foundation.h>
void exchange (int *pint1,int *pint2)
{
int temp;
temp = *pint1;
*pint1 = *pint2;
*pint2 = temp;
}
int mian (int argc, char *argv[])
{ @autoreleasepool{
void exchange (int *pint1,int *pint2);
}
return 0;
}
运行结果为:
i1 = -5, i2 = 66
i1 = 66, i2 = -5
i1 = -5, i2 = 66
3.指针和数组
当定义用于指向数组元素的指针时,不能将指针定义为“数组指针”,而是要将指针定义为数组中所包含的元素类型。
例如:如果你有一个Fraction对象数组fracts。同样,可以通过下面的语句定义一个指针,用于指向fracts中的元素:
Fraction **fractsPtr;
fractsPtr = fracts;这里并没有用到地址运算符,因为编译器将没有下表的数组名称看作是指向数组第一个元素的指针。要产生指向首元素的指针,还有另一个等效方式,就是对数组第一个元素应用地址运算符,例如:
fractsPtr = &fracts[0];
对数组使用指针的真正的好处体现在按顺序访问数组元素时。一般情况下,可以使用表达式
* (fractsPtr + i)来访问fractsPtr[i]的数值。
一般来说,如果a是元素类型为x的数组,px是“x指针”类型,并且i 和n都是整型变量,则表达式
px = a;
将px设置为指向a的第一个元素,而后,表达式
*(px + I)
指向啊[i]中包含的值,此外,表达式
px +=n;将px设置为指向数组中比原来指向的元素多了n个元素的指针,无论数组中包含的元素是什么类型。
处理指针时,运用自增和自减运算符非常方便。对指针应用自增运算符相当于将指针加1,而对指针应用自减运算符则相当于将指针减1(这里的“1”代表一个单元,或时指声明指向的数据元素的大小)。
比较两个指针变量的做法是完全合法的。这里比较指向同一数组的两个指针时是非常有用的。你可以测试指针valuePtr,看它的指向是否超出了包含100个元素的数组的范围,方法是将它们与指数组最后一个元素的指针相比较,例如表达式
valuesPtr > &values[99];
也可以写成:
valuesPtr > values + 99;
或valuesPtr > &values[0] + 99;
当数组作为参数时,如果要使用索引数来引用数组元素,那么要将对应的行参声明为数组。这样能够准确地反应该函数对数组地使用情况。类似地,如果要将参数作为指向数组的指针,则要将其声明为指针类型。
字符串指针
指向数组的指针最广泛的应用之一,就是作为字符串指针,原因是符号表达的便利和效率。
现在编写一个cpoyString的函数,则可以如下编码:
如果使用常规数组索引方式编写这个函数
void copyString (char to[], char from[])
{
int i;
for (i=0; from[i] != '\0'; ++i )
to[i] = form[i];
to[i] = '\0';
}
如果使用指针编写
void copyString (char *to,char *from)
{
for (; *from != '\0';++from,++to)
*to = *from;
*to = '\0';
}
可以用copyString (string2, "So is this.");这意味着向函数传递字符串作为参数时,实际上传递的是指向该字符串的指针。要记住,只要用到字符串,就会产生指向该字符串的指针。
现在声明字符串指针textPtr:
char *textPtr;
那么表达式
textPtr = “A character string.”;
则将textPtr设为指向字符串常量“A character string.”的指针。需要注意字符串指针和字符数组的区别,因为前面显示的赋值类型对于字符数组并不合法。例如:
char text[100];
后就不能编写如下的代码:
text = "This is not valid.";
只有在初始化字符数组时,才允许对字符数组使用这种赋值方式,如下语句:
char text[80] = "This is ok";
以这种方式初始化text时,并没有在text中存储指向字符串串“This is ok"的指针,而是在相应的text数组元素中储存实际的字符本身及最后的终止空字符。
如果text是一个字符指针,则使用以下表达式初始化text:
char *text = "This is ok" ;将赋给它上一个指向字符串 "This is ok"的指针。
4.指针运算
指针所允许的其他唯一操作就是相同类型的两个指针得到减法,这样,两个指针相减的结果就是他们之间所包含的元素个数。
函数指针
声明函数指针时,编译器不但需要知道指向函数的指针变量,而且要知道函数返回值的类型,以及参数的数目和类型。要声明变量fnPtr为“指向返回int并且不带参数的函数指针”,可以编写如下代码:
int (*fnPtr) (void);
要使函数指针指向特定函数,可以简单地将函数名称赋给该指针。如果lookup是返回int并且不带参数的函数,则语句:
fnPtr = lookup;
即将函数lookup函数的指针存入函数指针变量fnPtr。编译器将自动产生指向特定函数特定函数的指针,允许在函数名称之前添加&标志,但这不是必须的。
如果之前没有定义函数lookup,则必须在函数实现前面的赋值运算前面声明该函数,如下语句:
int lookup (void);
通过对指针变量应用函数调用运算符,同时在括号内列出该函数的所有参数,就可以间接引用指针变量引用的函数。比如:
entry = fnPtr();
函数指针的一个常见应用就是将其作为参数传递给其他函数。Standard Lirbary中qsort就这样,该函数实现数组元素的快速排序。只要比较待排序的数组中的两个元素时,就调用这个函数。使用这种方式,qsort可以对任何类的数组进行排序,因为数组中任意两个元素进行比较都是由用户提供的函数实现的。
函数指针的另一应用就是建立分派表。可以在数组中存储函数指针,这样,就可以创建包含要调用的函数指针的表。
5.指针和内存地址
计算机的内存可以理解为存储单元的顺序集合,计算机内存中的每一个单元都有一个相关的编号,称为地址。通常,计算机内存的首地址为0。
计算机使用内存来存储计算机程序的指令和相关变量的值。每个变量都有唯一的内存地址。对变量运用地址运算符,产生的值是变量在计算机内存中的实际存储地址。对变量应用间接寻址运算符,就是将包含在指针变量中的数值作为内存地址,然后获取该内存地址中存储的值,并按照指针变量声明的类型进行解释。
6.他们都不是对象
知道来如何定义数组,结构,字符串,但是要记住它们都不是对象。这意味着不能够为他们传递消息,也不能利用它们获得Foundation框架提供的内存分配策略之类的最大优势。所以更希望使用Foundation中将数组和字符串定义对象的类,而不是使用该语言的内置类型。