数组和指针
下面复习一下什么是数组,什么是指针:
● 数组是相同类型的对象集合,可以用一个名称引用。例如,数组scores[50]可以含有50场篮球季赛的比分。使用不同的索引值可以引用数组中的每个元素。scores[0]是第一个分数,scores[49]是最后一个分数。如果每个月有10场比赛,就可以使用多维数组scores[12][10]。如果一月开始比赛,则五月的第3场比赛用scores[5][2]引用。
● 指针是一个变量,它的值是给定类型的另一个变量或常量的地址。使用指针可以在不同的时间访问不同的变量,只要它们的类型相同即可。
数组和指针似乎完全不同,但它们有非常密切的关系,有时还可以互换。下面考虑字符串。字符串是char类型的数组。如果用scanf()输入一个字符,可以使用如下语句:
char single;
scanf("%c", &single);
这里,scanf()需要寻址运算符,因为scanf()需要存储输入数据的地址。然而,如果读入字符串,可以编写如下代码:
char multiple[10];
scanf("%s", multiple);
这里不需要使用&运算符,而使用了数组名称,就像指针一样。如果以这种方式使用数组名称,而没有带索引值,它就引用数组的第一个元素的地址。
但数组不是指针,它们有一个重要区别:可以改变指针包含的地址,但不能改变数组名称引用的地址。
下面通过几个例子来了解数组和指针如何一起使用。这些例子串在一起,构成一个完整的练习。通过这些练习,很容易掌握指针的基本概念及其和数组的关系。
试试看:数组和指针
这个例子进一步说明了,数组名称本身引用了一个地址,执行以下程序:
/* Program 7.4 Arrays and pointers - A simple program*/
#include <stdio.h>
int main(void)
{
char multiple[] = "My string";
char *p = &multiple[0];
printf("/nThe address of the first array element : %p", p);
p = multiple;
printf("/nThe address obtained from the array name: %p/n", p);
return 0;
}
在某台计算机上的输出如下所示:
The address of the first array element : 0x0013ff62
The address obtained from the array name: 0x0013ff62
代码的说明
可以从这个程序的输出中得到一个结论:&multiple[0]会产生和multiple表达式相同的值。这正是我们期望的,因为multiple等于数组第一个字节的地址,&multiple[0]等于数组第一个元素的第一个字节,如果它们不同,才令人惊讶。如果p设置为multiple,而multiple的值与&multiple[0]相同,那么p+1等于什么。试试下面的例子。
试试看:数组和指针(续)
这个程序说明了将一个整数值加到指针上的结果:
/* Program 7.5 Arrays and pointers taken further */
#include <stdio.h>
int main(void)
{
char multiple[] = "a string";
char *p = multiple;
for(int i = 0 ; i<strlen(multiple) ; i++)
printf("/nmultiple[%d] = %c *(p+%d) = %c &multiple[%d] = %p p+%d = %p",
i, multiple[i], i, *(p+i), i, &multiple[i], i, p+i);
return 0;
}
输出如下:
multiple[0] = a *(p+0) = a &multiple[0] = 0x0013ff63 p+0 = 0x0013ff63
multiple[1] = *(p+1) = &multiple[1] = 0x0013ff64 p+1 = 0x0013ff64
multiple[2] = s *(p+2) = s &multiple[2] = 0x0013ff65 p+2 = 0x0013ff65
multiple[3] = t *(p+3) = t &multiple[3] = 0x0013ff66 p+3 = 0x0013ff66
multiple[4] = r *(p+4) = r &multiple[4] = 0x0013ff67 p+4 = 0x0013ff67
multiple[5] = i *(p+5) = i &multiple[5] = 0x0013ff68 p+5 = 0x0013ff68
multiple[6] = n *(p+6) = n &multiple[6] = 0x0013ff69 p+6 = 0x0013ff69
multiple[7] = g *(p+7) = g &multiple[7] = 0x0013ff6a p+7 = 0x0013ff6a
代码的说明
注意输出中右边的地址列表。p设置为multiple的地址,p+n就等于multiple+n,所以multiple[n]与*(multiple+n)是相同的。地址加上了1,对于元素占用一个字节的数组来说,这正是我们期望的。从输出的两列中可以看出,*(p+n)是给p中的地址加上整数n,再对得到的地址取消引用,就计算出了与multiple[n]相同的结果。
试试看:不同类型的数组
这很有趣,计算机可以将多个数字加在一起。下面改变数组的类型,看看会发生什么:
/* Program 7.6 Different types of arrays */
#include <stdio.h>
int main(void)
{
long multiple[] = {15L, 25L, 35L, 45L};
long * p = multiple;
for(int i = 0 ; i<sizeof(multiple)/sizeof(multiple[0]) ; i++)
printf("/naddress p+%d (&multiple[%d]): %d *(p+%d) value: %d",
i, i, p+i, i, *(p+i));
printf("/n Type long occupies: %d bytes/n", sizeof(long));
return 0;
}
编译并执行这个程序,得到完全不同的结果:
address p+0 (&multiple[0]): 1310552 *(p+0) value: 15
address p+1 (&multiple[1]): 1310556 *(p+1) value: 25
address p+2 (&multiple[2]): 1310560 *(p+2) value: 35
address p+3 (&multiple[3]): 1310564 *(p+3) value: 45
Type long occupies: 4 bytes
代码的说明
这里将printf()函数的第二个参数及后面的参数用空格分隔开,以便于看出格式指定符和参数之间的对应关系。这次,指针p设置为multiple的地址,而multiple是long类型的数组。该指针最初包含数组中第一个字节的地址,也就是元素multiple[0]的第一个字节。地址用指定符%d显示,所以它们都是十进制值,这将易于看出后续地址的区别。
注意看输出。在这个例子中,p是1 310 552 ,p+1是1 310 556 ,而1 310 556 比1 310 552 大4,但我们仅给p加上了1。这并没有错。编译器知道,给地址值加1时,就表示要访问该类型的下一个变量。这就是为什么声明一个指针时,必须指定该指针指向的变量类型。char类型存储在一个字节中,long变量一般占用4个字节。在计算机上声明为long的变量占4个字节,给long类型的指针加1,结果是给地址加4,因为long类型值占4个字节。如果计算机在8个字节中存储long类型,则给指向long的指针加1,会给地址值加8。
注意,这个例子可以直接使用数组名称。编写for循环,如下所示:
for(int i = 0 ; i<sizeof(multiple)/sizeof(multiple[0]) ; i++)
printf(
"/naddress multiple+%d (&multiple[%d]): %d *(multiple+%d) value: %d",
i, i, multiple+i, i, *(multiple+i));
这个循环可以执行,因为表达式multiple和multiple+i都等于一个地址。我们输出这些地址的值,再使用*运算符输出这些地址存储的值。地址的算术运算规则与指针p相同。给multiple加1,会得到数组中下一个元素的地址,即内存中multiple后面的4个字节。但注意,数组名称是一个固定的地址,而不是一个指针。