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

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介:

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

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

相关文章
|
11月前
|
缓存 安全 编译器
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
《C++面试冲刺周刊》第三期聚焦指针与引用的区别,从青铜到王者级别面试回答解析,助你21天系统备战,直击高频考点,提升实战能力,轻松应对大厂C++面试。
928 132
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
|
9月前
|
安全 Java 容器
告别空指针噩梦:Optional让Java代码更优雅
告别空指针噩梦:Optional让Java代码更优雅
541 94
|
10月前
|
C++ Windows
应用程序无法正常启动(0xc0000005)?C++报错0xC0000005如何解决?使命召唤17频频出现闪退,错误代码0xC0000005(0x0)
简介: 本文介绍了Windows应用程序出现错误代码0xc0000005的解决方法,该错误多由C++运行库配置不一致或内存访问越界引起。提供包括统一运行库配置、调试排查及安装Visual C++运行库等解决方案,并附有修复工具下载链接。
2527 1
|
11月前
|
存储 C++
C++语言中指针变量int和取值操作ptr详细说明。
总结起来,在 C++ 中正确理解和运用 int 类型地址及其相关取值、设定等操纵至关重要且基础性强:定义 int 类型 pointer 需加星号;初始化 pointer 需配合 & 取址;读写 pointer 执向之处需配合 * 解引用操纵进行。
772 12
|
存储 安全 C语言
C++ String揭秘:写高效代码的关键
在C++编程中,字符串操作是不可避免的一部分。从简单的字符串拼接到复杂的文本处理,C++的string类为开发者提供了一种更高效、灵活且安全的方式来管理和操作字符串。本文将从基础操作入手,逐步揭开C++ string类的奥秘,帮助你深入理解其内部机制,并学会如何在实际开发中充分发挥其性能和优势。
|
API 数据安全/隐私保护 C++
永久修改机器码工具, exe一机一码破解工具,软件机器码一键修改工具【c++代码】
程序实现了完整的机器码修改功能,包含进程查找、内存扫描、模式匹配和修改操作。代码使用
|
C++
爱心代码 C++
这段C++代码使用EasyX图形库生成动态爱心图案。程序通过数学公式绘制爱心形状,并以帧动画形式呈现渐变效果。运行时需安装EasyX库,教程链接:http://【EasyX图形库的安装和使用】https://www.bilibili.com/video/BV1Xv4y1p7z1。代码中定义了屏幕尺寸、颜色数组等参数,利用随机数与数学函数生成动态点位,模拟爱心扩散与收缩动画,最终实现流畅的视觉效果。
1303 0
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
478 5
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。