美文网首页
cpp数组(三):数组指针

cpp数组(三):数组指针

作者: 浩波千里 | 来源:发表于2019-05-20 01:59 被阅读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.

    • 注意括号的优先级比*运算符高。
    • 如前所述, *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)相同。


    其余文章:

    数组与指针基础内容:

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

    相关文章

      网友评论

          本文标题:cpp数组(三):数组指针

          本文链接:https://www.haomeiwen.com/subject/mllnaqtx.html