cpp数组(三):数组指针

作者邮箱:z_zhanghaobo@163.com
github相关: https://github.com/HaoboLongway/simple_examples

指针提供一种以符号形式使用地址的方法。因为计算机硬件指令非常依赖地址,指针能够把想要传达的指令以更接近机器的方式表达。因此,使用指针的程序(一般地)更有效率。我们将会看到,指针能够有效处理数组,数组表示法其实是在变相使用指针。

关于数组及其指针间的联系,主要需要了解以下几个基础点

  1. 数组名是该数组首元素的地址
    例如,如果flizny是一个数组,那么有flizny == &flizny[0] //& 取地址运算符成立
...
int main()
{
    short dates[SIZE]; //short类型占用2字节
    short *pt1;
    short index;
    double bills[SIZE];  //double类型占用8字节
    double *pt2;
    //由于硬件不同,上述字节占用可能如上所示

    pt1 = dates;
    pt2 = bills;
    cout<<setw(23)<<"short"<<setw(10)<<"double"<<"\n";
    for(index=0; index<SIZE; index++){
        cout<<"pointer + "<<index<<": "<<setw(10)<<pt1 + index<<setw(10)<<pt2 + index<<"\n";
    }
    return 1;
    }
...
/////////////
//输出结果
                  short    double
pointer + 0:   0x6dfedc  0x6dfeb8
pointer + 1:   0x6dfede  0x6dfec0
pointer + 2:   0x6dfee0  0x6dfec8
pointer + 3:   0x6dfee2  0x6dfed0

如果从简单的加法考虑,应该有0x6dfedc + 1 == 0x6dfedd, 0x6dfeb8 + 1 == 0x6dfeb9成立,可是这里显示的地址又是怎么回事?
其实,指针加一指的是增加一个存储单元, 对数组而言,这意味着加一后的地址是下一个元素的地址,而非下一个字节的地址。


题目3-1-1:
给出下面代码

...
int main()
{
    int test_arr[6] = {1,2,4};
    cout<<test_arr<<endl;
    cout<<sizeof (int)<<endl;
    cout<<test_arr+3<<endl;
    cout<<*(test_arr + 4)<<endl;
    cout<<*test_arr + 4<<endl;
    return 1;
    }

已经知道上述部分输出为

0x6dfee8
4
...

求后三行输出结果

答案0x6dfef4, 0, 5.

  • 注意括号的优先级比*运算符高。
  • 如前所述, *test_arrtest_arr[0]是等价的。

  1. 多维数组的指针表示法
    进行以下实验:
    (关于格式化字符的输出具体参考https://www.runoob.com/cprogramming/c-function-printf.html)
#include <stdio.h>
#include <stdlib.h>
using namespace std;

int main(){
    int zippo[4][2] = {{2,4}, {6, 8}, {1, 3}, {5, 7}};
    //这里使用了printf函数作格式输出,在C++里面需要导入<stdio.h>以及<stdlib.h>才能使用
    printf("      zippo = %p,    zippo + 1 = %p\n", zippo, zippo+1);
    printf("   zippo[0] = %p, zippo[0] + 1 = %p\n", zippo[0], zippo[0]+1);
    printf("     *zippo = %p,   *zippo + 1 = %p\n", *zippo, *zippo+1);
    printf("zippo[0][0] = %d\n", zippo[0][0]);
    printf("  *zippo[0] = %d\n", *zippo[0]);
    printf("    **zippo = %d\n", **zippo);
    printf("               --End--");
    return 1;
}

该程序正常输出为:
(注意获取zippo及其他有关获取地址的操作在不同机器上执行结果不同)

      zippo = 0060FEF0,    zippo + 1 = 0060FEF8
   zippo[0] = 0060FEF0, zippo[0] + 1 = 0060FEF4
     *zippo = 0060FEF0,   *zippo + 1 = 0060FEF4
zippo[0][0] = 2
  *zippo[0] = 2
    **zippo = 2
               --End--

可以发现其中的特点(先不要看下面的整理,你发现编译器是如何对个指针做出反应的吗?)

  • zippo的地址与一维数组zippo[0]的地址相同,当然也有*zippo == zippo[0]成立
  • zippo与zippo + 1相差的地址有八个字节,这是因为zippo的元素是一维数组,而该数组占内存长度应当是其元素个数(即2)×数据类型所占字节数(即4)=8.

从指针的角度,解引用一个指针,得到引用对象代表的值,因为zippo[0]是该数组首元素(zippo[0][0])的地址,所以*(zippo[0])表示储存在zippo[0][0]上的值。与此类似,*zippo代表该数组首元素(zippo[0])的值,但是zippo[0]本身是一个int类型值的地址,该值的地址是&zippo[0][0],所以*zippo就是&zippo[0][0]
简而言之,zippo地址的地址,必须解引用两次才能够获取原始值,这是双重间接(double indirection)的例子

那么,现在你能够分析出为什么*(*(zippo+2) + 1)zippo[2][1]等价了吗?

  • zippo 二维数组首元素的地址
  • zippo + 2 二维数组第三个元素的地址
  • *(zippo+2) 二维数组第三个元素(是一个一维数组)首元素(是一个int型)的地址
  • *(zippo+2)+1 二维数组第三个元素的第二个元素(这就是zippo[2][1]了)的地址
  • *(*(zippo+2) + 1) 得到了zippo[2][1]的值

所以,对于多维数组,指针表示法有时令人迷惑,当要获取值时,最好采用数组表示法.


题目3-2-1:
指出下面代码的输出结果:

char *a[] = {"I", "like", "C++"};
    char **pa = a;
    pa++;
    cout<<*pa<<endl;

答案like

  • 注意这里字符串如"C++"相当于一个一维数组,而a即是多维数组,这里运用上面的分析方法,不难分析出结果。
  • 同时这里char *a[]的声明方式也值得注意

  1. 函数对数组的调用
    我们知道数组名是该数组首元素的地址,作为实际参数的数组名要求形参是一个与之配套的指针。只有在这种情形下,C++才会把int arr[]int *arr解释成一样的东西,也就是说,arr是一个指向int的指针。由于函数原型可以省略参数名,所以下面4种原型都是等价的:
  • int sum(int *arr, int n);
  • int sum(int *, int n);
  • int sum(int arr[], int n);
  • int sum(int [], int n);
    简单了解了调用的形参之后,当一个函数需要用到一个数组中的数据时,我们通常需要将该数组(引用或指针)以及其元素个数传入,就像下面这个对所有元素求和的例子
int sum_one(int arr[], int n){
    int sum=0;
    for (int i=0; i<n; i++){
        sum += *(arr + i);
    }
    return sum;
}

这个例子十分简单,也并非是仅有的方式,我们还可以向函数传递两个指针,如下例:

int sum(int * start, int * end){
    int sum = 0;
    while (start < end){    //保证循环最后处理的一个元素是end所指向位置的前一个元素
        sum += *(start);
        start ++;
    }

    return sum;
}

我们可以像是这样调用sum函数:

...
int test_ar[10] = {1, 2, 3, 4, 5};
cout<<sum(test_ar, *(test_ar + 5));
...

这样会返回test_ar的前五个元素的加和。此外,我们还可以把循环体压缩成一句 sum += *start++注意这里一元运算符*++优先级是同级的,但由于结合律是从右到左,所以start++先求值(后缀模式,表达式值是递增前的),然后是对其的解引用。
如果用*(start++)替换上述会更清楚些。


题目3-3-1
预测输出结果,并运行测试。

...
int test_ar[size] = {1, 2, 3, 4, 8, 3, 5};
int *p1, *p2, *p3;
    p1=p2=p3=test_ar;
    p3 += 2;
    cout<<"*p1 = "<<*p1<<'\t'<<"*p2 = "<<*p2<<'\t'<<"*p3 = "<<*p3<<endl;
    cout<<"*p1++ = "<<*p1++<<'\t'<<"*++p2 = "<<*++p2<<'\t'<<"(*p3)++ = "<<(*p3)++<<endl;
...

答案

*p1 = 1 *p2 = 1 *p3 = 3
*p1++ = 1       *++p2 = 2       (*p3)++ = 3

可以看出*p1++*(p1++)语义是相同的,*++p2*(++p2)相同。


其余文章:

数组与指针基础内容:

指针运算
认识指针
数组指针
数组定义及其初始化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,392评论 0 2
  • 第四天 数组【悟空教程】 第04天 Java基础 第1章数组 1.1数组概念 软件的基本功能是处理数据,而在处理数...
    Java帮帮阅读 1,612评论 0 9
  • 前言:复杂类型说明 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解...
    有理想有暴富的小青年阅读 598评论 0 4
  • 1. “我要出门了”,千代一边说着,一边从卧室里走了出来,一边检查手里的小包是否佩带齐全。 “打扮得漂漂亮亮的,一...
    筱念凉阅读 342评论 0 1
  • 添加好友, 已通过/已拒绝。 通过的大多是认识的人, 或一天两天, 或一年两年, 拒绝的大都是不认识的, 心想,我...
    wb風吹蘿叶飛阅读 104评论 0 0