指针与数组


二级指针与二维数组

char *string[] ={"abc","abcd","acf"};
char string[3][4]={"abc","abcd","acf"};
`
首先一点的是,虽然二维数组的数组名可以看做是一个指针,但是并不能将二维数组的数组名赋值给一个二级指针,也就是如下的代码

int main(void)
{
    int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
    int **p = arr;
    return 0;
}

上面的代码在执行的时候会报错,这是因为两者数据类型是不相同的,所以不能赋值。
二维数组的数组名指向的是一维数组,也就是指向数组类型,但是二级指针指向的是一级指针,也就是指针类型,所以两者不能互相赋值。

下面详细介绍指针与二维数组的关系
声明一个二维数组

int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (i=0;i<3;i++)
    {   for(j=0;j<4;j++)
        printf("%p ->%d\n",&arr[i][j],arr[i][j]);
    }
000000000062FE10 ->1
000000000062FE14 ->2
000000000062FE18 ->3
000000000062FE1C ->4
000000000062FE20 ->5
000000000062FE24 ->6
000000000062FE28 ->7
000000000062FE2C ->8
000000000062FE30 ->9
000000000062FE34 ->10
000000000062FE38 ->11
000000000062FE3C ->12

二维数组a可以看成是由a[0],a1,a[2]三个元素组成的一维数组,a是其数组名;a[0],a1,a[2]又都是一维数组,其数组名分别是a[0],a1,a[2],a[0],a1,a[2]分别表示各自数组的首个元素的地址。

arr[0]=000000000062FE00 ->1
arr[1]=000000000062FE10 ->5
arr[2]=000000000062FE20 ->9

由于数组名就是指针,故a[0],a1,a[2]为一级指针,其类型为int。a是由三个一级指针组成的数组。

那么a与a[0],a+1与a[0]+1的区别是什么?



a+1跨过了二维数组的一行,故a,a+1和a+2为行地址。
相对于a[0]而言,a[0]+1只跨过了二维数组的一个元素,故为列地址。
由此可以看出:
(1)a+i是第i行的地址,即*(a+i)=a[i]。因此a[i][j]等价于(*(a+i))[j];
(2)a[i]+j是第i行第j列的地址,因此*(a[i]+j)等价与a[i][j]。

上述等价关系总结如下:

a[i][j] ←→ (*(a+i))[j] ←→ *(a[i]+j) ←→ *(*(a+i)+j)

要想理解指针和二维数组之间的关系,就要首先弄清楚各种数组名所代表的元素。
数组名表示的是该数组首个元素的地址,即arr=&arr[0][0];

二维数组在内存中也是以一维数组的形式存储,只不过每个的一维数组的元素都是一维数组,因此二维数组名,可以将它看做是指向行数组的指针
对于二维数组的存储,是按照先行后列的形式排列的,把每一行看做是一个一位数组,那二维数组就是由多个一位数组组成的数组,即二维数组是数组的数组。

对于二维数组名,可以将它看做是指向行数组的指针,也就是说二维数组的元素是行数组,所以对于二维数组加减的变化是以行数组的大小为单位的,即arr指向arr[0]这个行数组,arr+1指向的是arr1这个行数组。对其进行解引用,得到的是每一行数组的首地址,即*arr表示的是第0行数组的首地址,和arr[0]相等,*(arr+1)表示的是第1行数组的首地址,和arr1是相等的。假如要取第1行第2个元素的地址,就是arr1+2,因为此时arr1代表的是一维数组,所以它的元素就是一个数字,在加减的时候移动的就是元素的大小,例如+1就表示该数组中第1个元素,+3就表示数组中的3个元素(以0开始)。因为
*(arr+1)和arr1相等,所以第1行第2个元素的地址也可以表示为*(arr+1)+2,对这个地址进行解引用,得到的就是数组元素的具体值,也就是((arr+1)+2)。
所以有如下公式,假如一个二维数组每行有N个元素,二维数组名是arr,那第i行第j个元素的地址是*(arr+i)+j,也可以表示为arr[i]+j。元素的值可以表示为*(*(arr+i)+j),或者可以表示为arr[i][j]。

对上述二维数组arr,虽然arr[0]、arr都是数组首地址,但二者指向的对象不同,arr[0]是一维数组的名字,它指向的是arr[0]数组的首元素,其值为arr[0]首个元素的地址,对其进行“*”运算,得到的是一个数组元素值,即arr[0]数组首元素值(本例arr[0]数组的首个元素值为1),因此,*arr[0]与arr[0][0]是同一个值。

而arr(可以看作arr+0)是一个二维数组的名字,它指向的是它所属元素的首元素,而二维数组的每一个元素都是一个维数组,因此arr指向二维数组arr的首个元素arr[0],对其进行*运算得到的是arr[0]的地址,而要想得到arr[0][0]的值,需要再进行一次*运算。

*arr=&arr[0];
**arr=*(&arr[0])=arr[0][0]
printf("arr=%p ->%d\n",arr,**arr); //两次解应用得到arr[0][0]
printf("*arr=%p->%d\n",*arr,**arr) ;
printf("arr[0]=%p ->%d\n",arr[0],*(arr[0]));   //arr[0][0]地址

printf("%p\n",&arr[0][0]); 
printf("%p\n",&arr[0]);
printf("%p\n",*(arr+2)+3);                     //&arr[2][3]
printf("%p\n",arr[2]+3);               
printf("%p ->%d\n",*(arr+2)+3,*(*(arr+2)+3));
printf("%p ->%d\n",&arr[2][3],arr[2][3]);
arr=000000000062FE00 ->1
*arr=000000000062FE00->1
arr[0]=000000000062FE00 ->1

000000000062FE00
000000000062FE00
000000000062FE2C
000000000062FE2C
000000000062FE2C ->12
000000000062FE2C ->12

因此,数组名最为指针时移动单位的是“行”,所以arr+i指向的是第i个行数组,即指向arr[i]。对arr进行“*”运算,得到的是一维数组arr[0]的首地址,即\arr与arr[0]是同一个值但指向类型不同通过(arr+1与arr[0]+1即能看出其不同)。当用int*p定义指针p时,p的指向是一个int型数据,对其进行加减操作,移动的是一个int型大小,而不是一个地址,因此,用arr[0]对p赋值是正确的,而用arr对p赋值是错误的。
使用数组指针对二级指针操作
而int (*ptr)[4],ptr表示一个指向4个int型数组的指针,对其进行加减操作,该变量为数组大小乘以数据类型大小。即ptr+1=&ptr+4*sizeof(int);
因而,int (*ptr)[4]可以使用arr来初始化,因为ptr是一个指向包含4个int型元素的数据,而arr指向其首个元素的地址,其首个元素又是一个包含4个int型数据的数据。

int (*ptr)[4]=arr;                              //  行地址
printf("arr=%p->%d\n",&arr[0][0],arr[0][0]);    //首元素地址
printf("ptr=%p->%d\n",ptr,**ptr);     
printf("ptr+1=%p->%d\n",ptr+1,**(ptr+1));       //行地址加一移动4个int型大小
printf("*(ptr+1)+1=%p->%d\n",*(ptr+1)+1,*(*(ptr+1)+1));  //
运行结果:
arr=000000000062FE00->1
ptr=000000000062FE00->1
ptr+1=000000000062FE10->5
*(ptr+1)+1=000000000062FE14->6

完整程序:

#include<stdio.h>

int main()
{
 int i,j=0;
    int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    //分别使用不同的形式打印第2行,第3个元素的值和地址
    //根据推到出来的规律如下
    //该元素的地址
    int  *p;                  // p+1 移动一个int 类型的大小  ,列地址
    arr+1 ;                   //移动一个int a[4] 类型的大小,行地址
   // p=arr;                    // 错误,类型不匹配
    p=*arr;                   // 合法,等同与p=*(a+0)
    p=arr[0];                 // 合法,arr[0]表示数组arr[0]的首个元素arr[0][0]地址.p=&arr[0][0]
        for(i=0;i<3;i++)
     printf("arr[%d]=%p ->%d\n",i,arr[i],*(arr[i]));
     
    int (*ptr)[4]=arr;
    printf("arr=%p->%d\n",&arr[0][0],arr[0][0]);
    printf("ptr=%p->%d\n",ptr,**ptr);
    printf("ptr+1=%p->%d\n",ptr+1,**(ptr+1));
     printf("*(ptr+1)+1=%p->%d\n",*(ptr+1)+1,*(*(ptr+1)+1));
    printf("arr=%p ->%d\n",arr,**arr); //两次解应用得到arr[0][0]
    printf("*arr=%p->%d\n",*arr,**arr) ;
    printf("arr[0]=%p ->%d\n",arr[0],*(arr[0]));   //arr[0][0]地址
    printf("\n");
    printf("%p\n",&arr[0][0]); 
    printf("%p\n",&arr[0]);
    printf("%p\n",*(arr+2)+3);              //&arr[2][3]
    printf("%p\n",arr[2]+3);               

    
    
    
    
    //该元素的值是12
    printf("%p ->%d\n",*(arr+2)+3,*(*(arr+2)+3));
    printf("%p ->%d\n",&arr[2][3],arr[2][3]);
     printf("\n");
   
    for (i=0;i<3;i++)
    {   for(j=0;j<4;j++)
        printf("%p ->%d\n",&arr[i][j],arr[i][j]);
    }
    return 0;
}

执行结果如下:

ptr=000000000062FE00->1
ptr+1=000000000062FE10->5
*(ptr+1)+1=000000000062FE14->6
arr=000000000062FE00 ->1
*arr=000000000062FE00->1
arr[0]=000000000062FE00 ->1

000000000062FE00
000000000062FE00
000000000062FE2C
000000000062FE2C
000000000062FE2C ->12
000000000062FE2C ->12

000000000062FE00 ->1
000000000062FE04 ->2
000000000062FE08 ->3
000000000062FE0C ->4
000000000062FE10 ->5
000000000062FE14 ->6
000000000062FE18 ->7
000000000062FE1C ->8
000000000062FE20 ->9
000000000062FE24 ->10
000000000062FE28 ->11
000000000062FE2C ->12

指针数组
char *strs[]={"abc","def","acfg"};
指针数组是一个元素均为指针的数组,数组的数组名表示数组首个元素的地址,而首个元素又是指针,因此指针数组的数组名就是一个二级指针。
二维数组名表示的是首个元素的地址,二维数组又可以理解为一个一维数组,而每个一维数组的元素又是
一个一维数组。对其进行加减操作,是以一维数组的大小为单位进行。类似与数组指针,即指向数组的指针,对其进行加减操作也是以数组大小为单位进行。

此处输入图片的描述
此处输入图片的描述

int douptr(char** a,int size)
{
    char **p=a;
    int c=strlen(*p+2);
    printf("a=%p\n",a);
    printf("p=%p\n",p);
    printf("%s\n",*p);
    printf("%c",**p);
    
}
int main()
{
    int i,j=0;
    char *strs[] = {"abc","def","acfg"};  
    
    int size=sizeof(arr)/sizeof(char *);      //数组大小
    printf("arr=%p\n",arr);                   //数组地址,数组名表示首个元素地址
    printf("&arr[0]=%p\n",&arr[0]);           //数组首个元素地址
    printf("&arr[1]=%p\n",&arr[1]);
    printf("&arr[2]=%p\n",&arr[2]);    
    printf("&arr[0]=%p\n",arr);               //数组首个元素地址
    printf("arr[0]->%s\n",*arr);              //数组首个元素指向内容
    printf("arr[1][1]=%c\n",*(*(arr+1)+1)) ;  //第二个元素指向数组的第二个元素
    printf("arr[1][1]=%c\n",arr[1][1]);       //  
    printf("size=%d\n",size);
    douptr(arr,size);

    return 0;
}

结果:

arr=000000000062FE30
&arr[0]=000000000062FE30
&arr[1]=000000000062FE38
&arr[2]=000000000062FE40
&arr[0]=000000000062FE30
arr[0]->abc
arr[1][1]=e
arr[1][1]=e
size=3
a=000000000062FE30
p=000000000062FE30
abc
a

参考文献
[1] http://ssspure.blog.51cto.com/8624394/1694224
[2] https://v.qq.com/x/cover/3dwhoro4c9voh54/h14042xdbdp.html
[3] http://ssspure.blog.51cto.com/8624394/1694224
[4] http://c.biancheng.net/cpp/html/478.html

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

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,422评论 3 44
  • 1、一维数组 定义一个指针P 赋值p=&a[0] a+i=&a[i]=p+i *(a+i)=a[i] 仅当p...
    PLW阅读 1,327评论 0 1
  • C语言中的指针与数组 @(C语言)[排序算法, 快速排序, C实现] 引言 相信指针与数组是不少同学在初学C语言时...
    harry502阅读 459评论 0 2
  • !!!注意:因为简书的显示格式缘故,所以“ * ”显示会出现问题,可能有些星号由于疏忽未改动格式,造成没有显示,请...
    Eric_Hunter阅读 499评论 0 1
  • 你是我心中的歌 吟唱出来是心脉的节奏 我的心曾满缚枷锁 你把它释放出来横冲直撞 它也曾尝试横流四海 但无论如何行走...
    落日丹枫阅读 180评论 0 1