5.8 指针与数组的关系
C++ 中指针与数组关系密切,几乎可以互换使用。数组名可以看成常量指针,指针可以进行任何有关数组下标的操作。
编程技巧 5.4
操作数组时用数组符号而用指针符号。尽管程序编译时间可能稍长一些.但程序更加清晰。
假设声明了整数数组 b[ 5 ]
和整数指针变量 bPtr。由于数组名(不带下标)是数组第一个元素的指针,因此可以用下列语句将 bPtr 设置为 b 数组第一个元素的地址:
bPtr = b;
这等于取数组第一个元素的地址,如下所示:
bPtr=&b[ O ];
数组元素 b[3]
也可以用指针表达式引用:
*( bPtr + 3 )
上述表达式中的3是指针的偏移量(offset)。指针指向数组开头时,偏移量表示要引用的数组元素,偏移量值等于数组下标。上述符号称为指针/偏移量符号(pointer/offset notation)。括号是必需的,因为*的优先顺序高于+的优先顺序。如果没有括号,则上述表达式将表达式 *bPtr
的值加上3(即 3 加到 b[0]
中,假设 bPtr 指向数组开头)。就像数组元素可以用指针表达式引用一样,下列地址:
&b[ 3 ]
可以写成指针表达式:
bPtr + 3
数组本身可以当作指针并在指针算法中使用。例如,下列表达式:
*( b + 3)
同样引用数组元素 b[3]
。一般来说,所有带下标的数组表达式都可以写成指针加偏移量,这时使 用指针/偏移量符号,用数组名作为指针。注意,上述语句不修改数组名,b还是指向数组中第一个元素指针和数组一样可以加下标。例如,下列表达式:
bPtr[ 1 ]
指数组元素 b[1]
,这个表达式称为指针/下标符号(pointer/subscript notation)。
记住,数组名实际上是个常量指针,总是指向数组开头。因此下列表达式:
b += 3
是无效的,因为该表达式试图用指针算法修改数组名的值。
常见编程错误 5.14
尽管数组是指向数组开头的S针,而指针可以在算术表达式中修改,但数组名不可以在算术表达式中修改,囚为数组名实际上是个常量指针。
图 5.20 的程序用我们介绍的四种方法引用数组元素(数组下标、用数组名作为指针的指针/偏移量符号、指针下标和指针的指针/偏移量符号,打印数组的的4个元素)。
要演示数组和指针的互换性,还可以看看程序5.21中的两个字符串复制函数 copy1 和 copy2。
这两个函数都是将字符串复制到字符数组中。比较copy1和copy2的函数原型可以发现,函数基本相同(由于数组和指针具有互换性)。这些函数完成相同的任务,但用不同方法实现。
1 // Fig. 5.20: f~g05_20.cpp
2 // Using subscripting and pointer notations with arrays
3
4 #include <iostream.h>
5
6 int main()
7{
8 int b[] = { 10, 20, 30, 40 } ;
9 int *bPtr = b; // set bPtr to point to array b
10
11 cout << "Array b printed with:\n"
12 << "Array subscript notation\n";
13
14 for(int i = 0; i < 4; i++ ),
15 cou << "b[ " << i << "] = << b[ i ] << '\n';
16
17
18 cout << "\nPointer/offset notation where\n"
19 << "the pointer is the array name\n";
2O
21 for (int offset = 0; offset < 4; offset++ )
22 cout << "* (b +" << offset << ") ="
23 << *( b + offset ) << '\n';
24
25
26 cout << "\nPointer subscript notation\n";
28 for ( i = 0; i < 4; i++ )
29 cout << "bPtr[" << i << "] =" << bPtr[ i ] << '\n';
31 cout << "\nPointer/offset notation\n";
32
33 for ( offset = 0; offset < 4; offset++ )
34 cout << "*(bPtr + "<< offset << ") ="
35 << * ( bPtr + offset ) << '\ n';
36
37 return 0;
38 }
输出结果:
Array b Printed with:
Array subscript notation
Pointer/offset notation where
the pointer is the array name
* (b + 0) = 10
* (b + 1) = 20
* (b + 2) = 30
* (b + 3) = 40
Pointer subscript notation
bPtr[ 0 ] = 10
bPtr[ 1 ] = 20
bPtr[ 2 ] = 30
bPtr{ 3 ] = 40
Pointer/offset notation
*(bPtr + 0) = 10
*(bPtr + 1) = 20
*(bPtr + 2) = 30
*(bPtr + 2) = 40
图 5.20 用我们介绍的四种方法引用数组元素
1 // Fig. 5.21: figOS_21.cpp
2 // Copying a string using array notation
3 // and pointer notation.
4 #include <iostream.h>
5
6 void copy1( char *, const char * );
7 void copy2( char *, const char * );
8
9 int main()
10 {
11
12 string3[ 10 ], string4[] = "Good Bye";
13
14 copy1( string1, string2 );
15 cout << "string1 =" << string1 << endl;
16
17 copy2( string3, string4 );
18 cout << "string3 = "<< string3 << endl';
19
20 return 0;
21 }
22
23 // copy s2 to sl using array notation
24 void copy1( char *s1, const char *s2 )
25 {
26 for ( int i = 0; ( s1[ i ] = s2[ i ] ) != '\0'; i++ )
27 ; // do nothing in body
28 }
29
30 // copy s2 to sl using pointer notation
31 void copy2( char *s1, const char *s2 )
32 {
33 for ( ; ( *s1 = *s2 ) != '\0'; s1++, s2++ )
34 ; // do nothing in body
35 }
输出结果:
string1 = Hello
string3 = Good Bye
图 5.21 使用数组和指针符号复制字符串
函数 copy1 用数组下标符号将 s2 中的字符串复制到字符数组 s1 中。函数声明一个作为数组下标的整型计数器变量 i。for 结构的首部进行整个复制操作,而for结构体本身是个空结构。首部中指定i初始化为 0,并在每次循环时加 1。for 的条件 (s1[i]=s2[i])!='\0'
,从 s2 向 s1 一次一个字符地进行复制操作。遇到 s2 中的 null 终止符时,将其赋给s1,循环终止,因为 null 终止符等于 '\0'
。
记住:赋值语句的值是赋给左边参数的值。
函数 copy2 用指针和指针算法将s2中的字符串复制到 s1 字符数组。同样是在 for 结构的首部进行整个复制操作,首部没有任何变量初始化。和 copy1 中一样,条件 (*s1=*s1)!='\0'
进行复制操作。
复引用指针 s2,产生的字符赋给复引用的指针 s1。进行条件中的赋值之后,指针分别移到指向 s1 数组的下一个元素和字符串 s2 的下一个字符。遇到 s2 中的 null 终止符时,将其赋给 s1,循环终止。
注意 copy1 和 copy2 的第一个参数应当是足够大的数组,应能放下第二个参数中的字符串,否则可能会在写人数组边界以外的内存地址时发生错误。另外,注意每个函数中的第二个参数声明为 const char*(常量字符串)。在两个函数中,第二个参数都复制到第一个参数,一次一个地从第二个参数复制字符,但不对字符做任何修改。因此,第二个参数声明为常量值的指针,实施最低权限原则。两个函数都不需要修改第二个参数,因此不向这两个函数提供修改第二个参数的功能。