本节书摘来自异步社区出版社《C和C++代码精粹》一书中的第2章,第2.7节,作者: 【美】Chuck Allison,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.7 指针和一维数组
C和C++代码精粹
在程序清单2.7中,会注意到在传递数组 s 时并没有使用它的地址,这是因为C和C++在大多数表达式中把数组名转换成指向它第一个元素的指针。自1984年以来,我已经向成百上千的学生讲授了C和C++,我注意到了指针和数组,特别是指针和多维数组之间的关系造成很多迷惑。
这样说似乎很奇怪,但是C++确实不支持数组,至少C++不像支持第一类数据类型如整型或者甚至结构体那样支持数组。考虑以下的语句:
int i=1,j;
int a[4]={0,1,2,3},b[4];
struct pair {int j,int y;};
pair p={1,2},q;
j=i; //OK:整型赋值
q=p; //OK:结构体赋值
b=a; //不能这样做
并不是所有有关数组的操作都是合法的。我们可以进行以下操作,但是它并不是一个“真实”的赋值:
int a[4]={0,1,2,3},*p;
p=a; /*只在p中存储了a[0]的地址*/
除了在声明中或者当一个数组名是sizeof运算符或&运算符的操作数之外,编译器总是把数组名解释成指向它的第一个元素的指针。可以将这个原则表达为:
a==&a[0]
或者等价于:
*a==a[0]
使用指针运算的规则,那么当把一个整型变量i和一个数组名相加,结果就得到指向数组第i个元素的指针,也就是:
a+i==&a[i]
或者,像我喜欢的表达方式一样:
重要的指针原则 2:*(a+i)==a[i]
程序清单2.8中的程序阐述了原则2以及准备步骤。
由于所有的数组下标是真正的指针运算,可以使用表达式i[a]代替a[i]。这些可直接从原则2中得到:
a[i]==*(a+i)==*(i+a)==i[a]
当然,任何使用了这样极端错误的表达的程序都会被中断而不被执行,而且程序员也会受到严厉的谴责。然而,使用相反的下标也不是完全没有道理,如果一个指针p传递一个数组,就可以使用表达式p[-1]重新得到在*p之前的元素,由于:
p[-1]==*(p-1)
程序清单 2.9极为全面地涵盖了指针和数组符号的结合,它也使用了一个关于数组中元素个数的有用公式:
size_t n=sizeof a/sizeof a[0];
虽然可以在除数中使用任何一个有效的下标,但0是最安全的,这是因为每个数组都有第0个元素。当然,这一习惯只有当原始的数组声明是在生存期内才适用。
对于那些愿意使用C风格字符串的人来说,一个遵循指针和数组符号概念之间相互作用的常用习惯是:
strncpy(s,t,n)[n]='\0';
程序清单2.8 说明数组名是指针
// array1.cpp: 用一个数组名作为一个指针
#include <iostream>
using namespace std;
main()
{
int a[] = {0,1,2,3,4};
int* p = a;
cout << "sizeof a == " << sizeof a << endl;
cout << "sizeof p == " << sizeof p << endl;
cout << "p == " << p << ", &a[0] == " << &a[0] << endl;
cout << "*p == " << *p << ", a[0] == " << a[0] << endl;
p = a + 2;
cout << "p == " << p << ", &a[2] == " << &a[2] << endl;
cout << "*p == " << *p << ", a[2] == " << a[2] << endl;
}
//输出:
sizeof a == 20
sizeof p == 4
p == 0x0012ff78, &a[0] == 0x0012ff78
*p == 0, a[0] == 0
p == 0x0012ff80, &a[2] == 0x0012ff80
*p == 2, a[2] == 2
程序清单2.9 使用索引和指针传递数组
// array2.cpp: 使用索引和指针传递数组
#include <iostream>
using namespace std;
main()
{
int a[] = {0,1,2,3,4};
size_t n = sizeof a / sizeof a[0];
//使用数组索引打印
for (int i = 0; i < n; ++i)
cout << a[i] << ' ';
cout << endl;
//你甚至可以交替a和i(但自己别这么做!)
for (int i = 0; i < n; ++i)
cout << i[a] << ' ';
cout << endl;
//使用指针打印
int* p = a;
while (p < a+n)
cout << *p++ << ' ';
cout << endl;
//和指针一起使用索引符是好的:
p = a;
for (int i = 0; i < n; ++i)
cout << p[i] << ' ';
cout << endl;
//和数组一起使用指针符是好的:
for (int i = 0; i < n; ++i)
cout << *(a+i) << ' ';
cout << endl;
//使用指针向后打印:
p = a + n-1;
while (p >= a)
cout << *p-- << ' ';
cout << endl;
//写在下方的负数是允许的:
p = a + n-1;
for (int i = 0; i < n; ++i)
cout << p[-i] << ' ';
cout << endl;
}
//输出:
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
4 3 2 1 0
4 3 2 1 0
这就把一个字符串拷贝到另一个字符串,同时确保没有溢出并且字符串没有被划上界限(假设n没有超限)—所有这些都在一个简短的语句当中实现。
在指针和数组名之间有另一个区别需要记住:一个数组名是一个不可改变的左值。这就意味着不能改变数组名对应的地址,就像下面的示例所尝试的那样:
int a[5],b[5],*p;
/*下面所有的都不合法*/
a++;
a=p+5;
b=a;
如果这样赋值,就会很轻易地丢失数组在内存中存储的位置(这可不是个好主意!)。
从字面上来说字符串是一组没有名称的字符,可以使用sizeof得到它们的大小并且甚至可以给它们添加下标(见程序清单2.10 和2.11)。注意在清单2.10中我的编译器把“hello”的每一次的出现都作为一个独立的对象,每次都返回不同的地址,有些编译器能够把具有相同字符的字符串“集中起来”以单个形式出现以节省空间。
程序清单2.10 说明一个字符串中的字符是一个匿名数组
// array3.cpp
#include <iostream>
using namespace std;
main()
{
char a[] = "hello";
char* p = a;
cout << "a == " << &a << ", sizeof a == " << sizeof a << endl;
cout << "p == " << (void*)p << ", sizeof p == " << sizeof p << endl;
cout << "sizeof \"hello\" == " << sizeof "hello" << endl;
cout << "address of \"hello\" == " << (void*)"hello" << endl;
cout << "address of \"hello\" == " << (void*)"hello" << endl;
}
//输出:
a == 0x0012ff84, sizeof a == 6
p == 0x0012ff84, sizeof p == 4
sizeof "hello" == 6
address of "hello" == 0x004090d4
address of "hello" == 0x004090f1
程序清单2.11 将字符串中的字符进行索引
// array4.cpp: 将字符串中的字符索引
#include <iostream>
using namespace std;
main()
{
for (int i = 0; i < 10; i += 2)
cout << "0123456789"[i];
}
//输出:
02468
练习 2.1
已知如下声明:
int a[ ] = { 10, 15, 4, 25, 3, -4 };
int *p = &a[ 2 ];
下面表达式的结果是什么?
a. *(p+1)
b. p[-1]
c. p-a
d. a[*p++]
e. *(a+a[2])
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。