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)相同。


其余文章:

数组与指针基础内容:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容

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