cpp数组(三):数组指针

2019-05-20  本文已影响0人  浩波千里

作者邮箱: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.


  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[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]等价了吗?

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


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

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

答案like


  1. 函数对数组的调用
    我们知道数组名是该数组首元素的地址,作为实际参数的数组名要求形参是一个与之配套的指针。只有在这种情形下,C++才会把int arr[]int *arr解释成一样的东西,也就是说,arr是一个指向int的指针。由于函数原型可以省略参数名,所以下面4种原型都是等价的:
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)相同。


其余文章:

数组与指针基础内容:

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

上一篇 下一篇

猜你喜欢

热点阅读