C/C++语言中的int a; int* a; int** a; int (*a)[]; int (*a)(int),重点介绍指针数组与数组指针

这一部分往往很少被总结,但是却经常用到,在使用的时候要注意区分。

先来一个基本的来入门吧!
参考自:
https://blog.csdn.net/qq_36744540/article/details/79832728

数组指针

首先,我们先介绍一下二维数组和二维数组的指针,二维数组相当于一个特殊的一维数组里面每个元素又是一个一维数组,例如:int a [ 3 ] [3],可以看成一个3行的一个列数组,每一列的元素又是一个长度为3的数组,数组名的一个特殊一维数组的首地址,如下:
在这里插入图片描述
a(第0行的首地址,也是可以是&a [0])指向a [0],对0行首地址元素的引用就有 a = a [0];
a + 1(第一行的首地址)指向a [1],有
(a + 1)= a [1];
a + 2(第二行的首地址)指向a [2],有*(a + 2)= a [2];
a + i(第i行的首地址)指向一个[i]中,有*(a + 1)= a [i]中。
而a[0],a[1],a[2]又是里面一维数组的数组名,因此a[i]可表示i行的首地址:
a[0](第0行第0个元素的地址)指向一个[0] [ 0 ],有*(a [ 0 ] +0)= a [0] [ 0 ];
a [0]+1(第0行第1个元素的地址)指向a [0] [1],有*(a [0] +1)= a [0] [1];
a [0] +2(第0行第2个元素的地址)指向a [0] [2],有*(a [0] +2)= a [0] [2]
总结:a[i]+j(第i行第j列个元素的地址)指向a[i][j], 有*(a[i]+j)=a[i][j],
即 * (*(a+i)+j)=a[i][j]。

二维数组的指针变量

对于指向二维数组的指针变量p,可以有两种:
一种是指向数组元素的列指针,一种是指向行的行指针,这时,p不是指向一个具体的数组元素,而是指向一个包含m个元素的一维数组。这里对行指针特殊说明一下;
行指针的定义形式为:类型标识符 (*指针变量名)[长度];
例如: int a[3][4],(*p)[4]=a; //这里也可以写为int a[3][4],(*p)[4]=&a[0];
其中p,p+1,p+2和a,a+1,a+2都是第i行的首地址。
使用指针变量访问二维数组的任意一个元素的方法
**(1)使用列指针:**定义一个列指针p,让它指向二维数组的第0个元素

int a[3][4];
int *p;
p=&a[0][0];   //因为a[0]是第0行的数组名,所以p=&a[0][0]相当于p=a[0],因为a[i][j]前面共有i*4+j个元素,该二维数组的任意i行j列元素可表示为*(p+i*4+j)

(2)使用行指针:定义一个行指针p,让它指向二维数组的第0行

int a[3][4];
int (*p)[4];
p=a;   //也可以为p=&a[0];

其中* ( *(p+i)+j)表示任意一个i行j列的元素。

多说一句,其实访问方式还可以很灵活的,跟直接使用数组名访问一样也可以,后面还会详细讨论。




参考自:https://www.cnblogs.com/zou107/p/4909847.html

一、关于二维数组和二维数组区别

(1)一维数组在内存中是连续分布存储的,同样,二维数组也是在内存连续存储的。所以从内存的角度来分析,一维数组和二维数组其实没有本质区别。

(2) 二维数组可以使用一维数组来代替。但是在实际的应用中,有时候使用二维数组会更加直观,方便程序的编程。

(3)两者在内存使用效率上是一模一样的。

二、二维数组的第一维和第二维概念

(1)例如 int a[2][5]中,前面的2表示第一维;后面的5表示第二维

(2) 二维数组的第一维表示最外部的那一层,第一维本身也是一个数组,里面存放了2个元素,这两个元素就分别是第二维的数组。第二维数组本身也是一个数组,里面存放的元素是普通的int型变量。
在这里插入图片描述

三、二维数组的下标访问

例1:

1      int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};
2      int (*p)[5]; // 定义一个数组指针
3      p = a;
4      
5      printf(" a[1][2]   = %d.\n", a[1][2]);     // a[1][2] = 8
6      printf("(*(p+1)+1) = %d.\n", *(*(p+1)+2)); // a[1][2]

运行结果:
在这里插入图片描述

四、关于二维数据必须要明白的几个符号

例2:理解 a 、&a、 a[0]、 &a[0]、 a[0][0]、 &a[0][0]

/*
    二维数组的几个符号的测试
    1、a等同于&a[0]
    2、a[0]等同于&a[0][0]
    3、在数值上 a、&a、a[0]、&a[0]、&a[0][0] 是相等的,但是在类型上面是有区别的。
*/
    int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};
    
    printf("a        = %p.\n", a);        // a       类型是 int (*)[5]
    printf("&a       = %p.\n", &a);       // &a      类型是 int (*)[2][5]
    printf("a[0]     = %p.\n", a[0]);     // a[0]    类型是 int *
    printf("&a[0]    = %p.\n", &a[0]);    // &a[0]   类型是 int (*)[5]
    printf("a[0][0]  = %d.\n", a[0][0]);  // a[0][0] 类型是 int 
    printf("&a[0][0] = %p.\n", &a[0][0]); // &a[0][0]类型是 int *

运行结果:
在这里插入图片描述

例3:第一维和第二维的数组指针的使用

// 二维数组与指针的结合使用

int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};

int (*p1)[5]; // 数组指针
int *p2;      // 一般指针
p1 = a;       // 等同 p1 = &a[0];     // 指向二维数组的数组名
p2 = a[0];    // 等同 p2 = &a[0][0];  // 指向二维数组的第一维数组

printf("a[0][2] = %d.\n", *(*(p1+0)+2));  // a[0][2] = 3
printf("a[1][2] = %d.\n", *(*(p1+1)+2));  // a[1][2] = 8
 
printf("a[0][2] = %d.\n", *(p2+2));       // a[0][2] = 3
printf("a[0][4] = %d.\n", *(p2+4));       // a[0][4] = 5

运行结果:
在这里插入图片描述

五、总结

(1)需要理解二维数组的实质和几个符号的含义。

(2)二维数组和数组指针两者是有紧密的关系的。学会使用数组指针来操作二维数组,重在实践操作使用就会加深理解。

**注:学习笔记部分是在朱有鹏老师物联网视频教程中摘取,特此声明。**http://www.zhulaoshi.org/




a) int a;表示一个内存空间,这个空间用来存放一个整数(int);
b) int* a;表示一个内存空间,这个空间用来存放一个指针,这个指针指向一个存放整数的空间,即a)中提到的空间;
c) int** a;表示一个内存空间,这个空间用来存放一个指针,这个指针指向一个存放指针的空间,并且指向的这个空间中的指针,指向一个整数。也简单的说,指向了一个b)中提到的空间;
d) int (a)[4];表示一个内存空间,这个空间用来存放一个指针,这个指针指向一个长度为4、类型为int的数组;和int* a的区别在于,++、+=1之后的结果不一样,其他用法基本相同。
以上四种类型见上图表示。
e) int (*a)(int);表示一个内存空间,这个空间用来存放一个指针,这个指针指向一个函数,这个函数有一个类型为int的参数,并且函数的返回类型也是int。

多说一句,都是指针,有时候可能值相同,但是意义却不同,使用方式或者说数据格式也不同。

重点:

int *p[]和int (*p)[]

前者是指针数组,后者是指向数组的指针。更详细地说。

前: 指针数组;是一个元素全为指针的数组.
后: 数组指针;可以直接理解是指针,只是这个指针类型不是int也不是char而是 int [4]类型的数组.(可以结合函数指针一并看看…)

int*p[4]------p是一个指针数组,每一个指向一个int型的
int (*q)[4]---------q是一个指针,指向int[4]的数组。

定义涉及两个运算符:“”(间接引用)、“[]”(下标),“[]”的优先级别大于“”的优先级别。小括号或者说圆括号优先级最大。

首先看int *p[4],“[]”的优先级别高,所以它首先是个大小为4的数组,即p[4];剩下的“int *”作为补充说明,即说明该数组的每一个元素为指向一个整型类型的指针。int *p[4]的存储结构如下:(存储方格横向排列或竖向排列没区别,只要按内存地址顺序排列就行,此处只是为画图方便)
在这里插入图片描述

再看int (q)[4]。它首先是个指针,即q,剩下的“int [4]”作为补充说明,即说明指针q指向一个长度为4的数组。int (*q)[4]的存储结构如下:

在这里插入图片描述

请看以下定义:

int a[2][4]={{2,5,6,8},{22,55,66,88}};

int c[4]={5,8,9,4};

int d[3]={23,12,443};

int *p[4],(*q)[4];

q=a;

*p=c;

*(p+1)=d;

则int *p[4]和int (*q)[4]的存储数据为:

在这里插入图片描述

在这里插入图片描述

验证:

#include <stdio.h>

int main(void)

{

    int a[2][4]={{2,5,6,8},{22,55,66,88}};

    int c[4]={5,8,9,4};

    int d[3]={23,12,443};

    int *p[4],(*q)[4];

    q=a;

    *p=c;

    *(p+1)=d;

    int i,j;

    for(i=0;i<2;i++)

        for(j=0;j<4;j++)

       {

           if((i==1)&&(j==3)) break;

           printf("*(*(p+%d)+%d)=%d\n",i,j,*(*(p+i)+j));

       }

    puts("===============");

    for(i=0;i<2;i++)

       for(j=0;j<4;j++)

           printf("*(*(q+%d)+%d)=%d\n",i,j,*(*(q+i)+j));

   return 0;

}

 

输出结果为:

*(*(p+0)+0)=5

*(*(p+0)+1)=8

*(*(p+0)+2)=9

*(*(p+0)+3)=4

*(*(p+1)+0)=23

*(*(p+1)+1)=12

*(*(p+1)+2)=443

===============

*(*(q+0)+0)=2

*(*(q+0)+1)=5

*(*(q+0)+2)=6

*(*(q+0)+3)=8

*(*(q+1)+0)=22

*(*(q+1)+1)=55

*(*(q+1)+2)=66

*(*(q+1)+3)=88

参考:
https://www.cnblogs.com/xiaojingang/p/4451089.html




最后,如果你愿意,还可以深入理解它们的使用,如下参考自:
https://www.cnblogs.com/mq0036/p/3382732.html

数组指针(也称行指针)

定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:

int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
 p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
 p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

指针数组

定义 int p[n];
[]优先级高,先与p结合成为一个数组,再由int
说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:

int *p[3];
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
for(i=0;i<3;i++)
p[i]=a[i]

这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:

*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]

优先级:()>[]>*

出处:http://www.cnblogs.com/hongcha717/archive/2010/10/24/1859780.html

=========================================================================

一、指针数组和数组指针的内存布局

初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。

下面到底哪个是数组指针,哪个是指针数组呢:

A)
int *p1[10];
B)
int (*p2)[10];

每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。

“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int 修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。我们可以借助下面的图加深理解:
在这里插入图片描述

二、int (*)[10] p2-----也许应该这么定义数组指针

这里有个有意思的话题值得探讨一下:平时我们定义指针不都是在数据类型后面加上指针变量名么?这个指针p2 的定义怎么不是按照这个语法来定义的呢?也许我们应该这样来定义
p2:

   int (*)[10] p2;

int (*)[10]是指针类型,p2 是指针变量。这样看起来的确不错,不过就是样子有些别扭。其实数组指针的原型确实就是这样子的,只不过为了方便与好看把指针变量p2 前移了而已。你私下完全可以这么理解这点。虽然编译器不这么想。_

三、再论a 和&a 之间的区别

既然这样,那问题就来了。前面我们讲过a 和&a 之间的区别,现在再来看看下面的代码:

int main()
{
   char a[5]={'A','B','C','D'};
   char (*p3)[5] = &a;
   char (*p4)[5] = a;
   return 0;
}

上面对p3 和p4 的使用,哪个正确呢?p3+1 的值会是什么?p4+1 的值又会是什么?毫无疑问,p3 和p4 都是数组指针,指向的是整个数组。&a 是整个数组的首地址,a是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据类型必须是相同的,如果不同需要显示或隐式的类型转换。p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下警告:
warning C4047: ‘initializing’ : ‘char (*)[5]’ differs in levels of indirection from ‘char *’。
还好,这里虽然给出了警告,但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。不过我仍然警告你别这么用。

既然现在清楚了p3 和p4 都是指向整个数组的,那p3+1 和p4+1 的值就很好理解了。

但是如果修改一下代码,把数组大小改小点,会有什么问题?p3+1 和p4+1 的值又是多少呢?

int main()
{
   char a[5]={'A','B','C','D'};
   char (*p3)[3] = &a;
   char (*p4)[3] = a;
   return 0;
}

甚至还可以把代码再修改,把数组大小改大点:

int main()
{
   char a[5]={'A','B','C','D'};
   char (*p3)[10] = &a;
   char (*p4)[10] = a;
   return 0;
}

这个时候又会有什么样的问题?p3+1 和p4+1 的值又是多少?

上述几个问题,希望读者能仔细考虑考虑,并且上机测试看看结果。

测试结果:
(1).char (p2)[5]=a;必须使用强制转换,如:char (p2)[5]=(char ()[5])a;
(2).把数组大小改变,都会编译不通过,提示:
error C2440: ‘initializing’ : cannot convert from 'char (
)[5]’ to ‘char ()[3]’
error C2440: ‘initializing’ : cannot convert from 'char (
)[5]’ to 'char (*)[10]'

(3).把以上程序测试代码如下:

int main()
{
  	char a[5]={'a','b','c','d'};
  	char (*p1)[5]= &a;
  	char (*p2)[5]=(char (*)[5])a;

  	printf("a=%d\n",a);
  	printf("a=%c\n",a[0]);
  	printf("p1=%c\n",**p1);
  	printf("p2=%c\n",**p2);
  	printf("p1+1=%c\n",**(p1+1));
  	printf("p2+1=%c\n",**(p2+1));

  	return 0;
}

输出:

a=1638208
a=a
p1=a
p2=a
p1+1=?
p2+1=?
Press any key to continue

结论:

根据指针类型及所指对象,表示指针大小,每次加1,表示增加指针类型大小的字节.----后面还会有解释说明.

四、地址的强制转换

先看下面这个例子:

struct Test
{
   int Num;
   char *pcName;
   short sDate;
   char cha[2];
   short sBa[4];
}*p;

假设p 的值为0x100000。如下表表达式的值分别为多少?

   p + 0x1 = 0x___ ?
   (unsigned long)p + 0x1 = 0x___?
   (unsigned int*)p + 0x1 = 0x___?

我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?

还记得前面我们的表达式“a+1”与“&a+1”之间的区别吗?其实这里也一样。指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte 而是元素的个数。所以:p + 0x1 的值为0x100000+sizof(Test)*0x1。至于此结构体的大小为20byte,前面的章节已经详细讲解过。所以p +0x1 的值为:0x100014。

(unsigned long)p + 0x1 的值呢?这里涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。

(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。

上面这个问题似乎还没啥技术含量,下面就来个有技术含量的:在x86 系统下,其值为多少?

int main()
{
   int a[4]={1,2,3,4};
   int *ptr1=(int *)(&a+1);//指向a数组后面的内存单元,&a+1表示向后移16个存储单元
   int *ptr2=(int *)((int)a+1);//表示a的存储单元的地址增加一个字节
   printf("%x,%x",ptr1[-1],*ptr2);//ptr1[-1]其实指向的是a数组的最后一个单元,*ptr1则表示a数组的地址后移一个字节之后的4个连续存储单元所存储的值
   return 0;
}

这是我讲课时一个学生问我的题,他在网上看到的,据说难倒了n 个人。我看题之后告诉他,这些人肯定不懂汇编,一个懂汇编的人,这种题实在是小case。下面就来分析分析这个问题:

根据上面的讲解,&a+1 与a+1 的区别已经清楚。

ptr1:将&a+1 的值强制转换成int类型,赋值给int 类型的变量ptr,ptr1 肯定指到数组a 的下一个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其值为0x4。
ptr2:按照上面的讲解,(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int类型的值赋给ptr2,也就是说ptr2 的值应该为元素a[0]的第二个字节开始的连续4 个byte 的内容。

其内存布局如下图:
在这里插入图片描述

好,问题就来了,这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面的值到底怎么存储的。这就涉及到系统的大小端模式了,如果懂汇编的话,这根本就不是问题。既然不知道当前系统是什么模式,那就得想办法测试。大小端模式与测试的方法在第一章讲解union 关键字时已经详细讨论过了,请翻到彼处参看,这里就不再详述。我们可以用下面这个函数来测试当前系统的模式。

int checkSystem()
{
  union check
  {
      int i;
      char ch;
  } c;
  c.i = 1;
  return (c.ch ==1);//如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。
}

如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。如果此函数的返回值为0 的话,*ptr2 的值为0x100。
出处:http://see.xidian.edu.cn/cpp/html/476.html

更多内容,可以参考:
https://blog.csdn.net/wtzdedaima/article/details/78300169
https://blog.csdn.net/qq_33573235/article/details/79530792
这篇博客可以说非常全面了。。
https://www.cnblogs.com/haore147/p/3647262.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值