《C和C++代码精粹》——2.7 指针和一维数组

    xiaoxiao2024-04-14  124

    本节书摘来自异步社区出版社《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])

    本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)