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

简介:

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

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

相关文章
|
2月前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
39 3
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
2月前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
2月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
16天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
50 0
|
2月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
60 4
|
2月前
|
算法 安全 C++
提高C/C++代码的可读性
提高C/C++代码的可读性
62 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
140 4
|
2月前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
50 2