一维数组名:首地址;指针常量
我们可以让指针指向一维数组名,然后再做相关操作
下面我们假设有一个二维数组,我们试图通过指针来对他进行操作:
#include<iostream>
using namespace std;
void main()
{
int i, j;
int A[3][3] = { {0,1,2},{3,4,5},{6,7,8} };
cout<< A <<endl;
cout << *A << endl;
cout << **A << endl;
}
结果:
我们可以看到,这里:
A和(*A)输出的都是一个地址(而这里的这个地址显然就是第0行第0列的地址)
只有(**A)输出的结果是一个数值;是首个元素的值本身
A:二维数组名
{
书P83:数组名是数组首(个)元素的内存首地址
书P94:数组名就是指向数组首地址(第一个元素地址)的指针常量
}
所以,这里A代表整个二维数组的首(个)元素(A[0])的内存首地址;
或者说A作为指针指向A[0](整个二维数组的第0行)的首(个)元素的地址;
{
A[0]是(一个)由【A[0]([0])、A[0]([1])、A[0]([2])】这几个数组元素所组成(构成)的一个一维数组
而A[0],就是【A[0]([0])、A[0]([1])、A[0]([2])】这些(所有的)元素所组成的一个一维数组的总和,以A[0]作为这(个)整个的名称(名字)
我们可以不恰当地说:(不严谨)
A[0]这个一维数组可以看作二维数组A[3][3]的一个子数组
}
而“A[0]的内存首地址”,即“一维数组的内存首(个元素的)地址”;
自然就是X系统给A[0]这个一维数组的首元素A[0][0]所安排的内存地址了。
*A:二维数组的一个一级指针
{
C语言日记 25 指针与数组_宇 -Yu的博客-CSDN博客
“a[i]”改为“*(a+i)”,运行情况相同
当然,把数组名作指针名,他们仍然会有区别:
如数组会自动分配空间,而指针不能;数组名不能赋值,但指针可以
}
*A作为一个一级指针,其实在本质上等价于A:(A[0]等价于(*A))
指向A[0](整个二维数组的第0行)的首(个)元素的地址;
而(**A)在这里的这个输出语句里面,并不代表二级指针
相当于*(*A),即:
求A[0](整个二维数组的第0行)的首(个)元素的值;
那么对于二维数组
我们能否像对付一维数组那样(书P92),定义一个指针指向数组的首元素然后一个一个输出呢?
显然可以:
#include<iostream>
using namespace std;
void main()
{
int i, j;
int A[3][3] = { {0,1,2},{3,4,5},{6,7,8} };
int* p;
p = *A;
for (i = 1; i <= 3; i++)
for(j = 1; j <= 3; j++)
{
cout << *p << " ";
p++;
}
}
结果:
但是我们在这里要知道:
我们在实际使用来输出的工具仍是一个一级指针,说白了我们输出的方式和我们之前已经学过的方法没有任何区别,我们对此没有任何长进;而且,这里我们不能直接使用数组名A:
#include<iostream>
using namespace std;
int main()
{
int i, j;
int A[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
int *p;
p = A;
for (i = 0; i < 3; i++)
for (j = 0; j < 5; j++)
cout <<1 << " ";
}
结果:
因此虽然以及指针就能(以二维数组构造的思维模式)输出整个数组,在这里我们还是应该把思维关注的重点放到二级指针之上。
那么我们是不是可以像书上说的那样,把二维数组名理解为一个指针的指针呢?
如果可以的话,即:
我们可以定义一个int **p(指针的指针),然后就可以将数组名赋给p,然后对二维数组进行相关操作;
like:
#include<iostream>
using namespace std;
int main()
{
int i, j;
int A[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
int **p;
p = A;
for (i = 0; i < 3; i++)
for (j = 0; j < 5; j++)
cout <<1 << " ";
}
可是结果:
也就是说:
书上说的所谓的“二维数组名为一个指针的指针”这个说法其实是完全错误的!!!
“无法从“int [3][5]”转换为“int **”已经完全表示了二维数组名不是指针的指针;
而从C语言日记 25 指针与数组_宇 -Yu的博客-CSDN博客的例6-4(2)里面我们也看到了,
正确使用的打开方式为:
#include<iostream>
using namespace std;
int main()
{
int a[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
int i, j;
int(*p)[5];
p = a;
for (i = 0; i < 3; i++)
for (j = 0; j < 5; j++)
cout << p [i][j] << " ";
}
或者说是:
#include<iostream>
using namespace std;
int main()
{
int a[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
int i, j;
int (*p)[5];
p = a;
for (i = 0; i < 3; i++)
for (j = 0; j < 5; j++)
cout << *(*(p + i) + j) << " ";
}
即:
先定义一个数组指针int (*p)[3],然后将数组名A赋给p,进行相关操作
即二维数组名是一个数组指针,p+1指向的是下一个数组(即下一行的地址)
{
数组指针:
指向数组地址的指针,本质上就是个指针;
指针数组:
数组元素为指针的数组,其本质为数组。(例如 int *p[3],定义了p[0],p[1],p[2]三个指针)
}
二维数组指针
具体例如:
int a[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
int(*p)[5];
p = a;
注释:
int(*p)[5]:
有点A[0]([0])的意思(味道),被赋址后指向的,就是数组名所代表的:
整个二维数组的首(个)元素(A[0])的内存首地址A[0]([0]);
所以我们可以说:
整个【(*p)[5]】(作为一个整体),被赋址后,就是一个指针
而拿他没有被赋址的时候单拎出来看,由于他被赋址后,就是一个指针,那么:
【(*p)[5]】自然就是一个指针变量;
也就是说拿【(*p)[5]】封装好作为整体来看,他和(*p)本质上也没有什么区别
具体拆解【(*p)[5]】来看:(拿他和二维数组A[x]([y])对照比较)
(*p)部分就相当于A[x]部分(行地址),[5]部分就相当于[y]部分(列地址);
(*p)[5]表示的,是(一个)由【(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3]、(*p)[4]】这几个数组元素所组成(构成)的一个一维数组;
同时这整个数组名,作为一个指针指向整个(这个)数组的首元素的地址(数组名不是本来就指向首地址嘛)
或者说,我们这里可以不严谨的不拘泥于符号,不拘泥于认为给我们规定好的死规矩:
p[5]本身就是一个一维数组(这个总是很正常,没什么奇怪的了吧)
如同a[i]==&a[i][0],*(p+i)==&a[i][0];
但是对于定义一个二维数组,我们可以让他“当p变为(p+1)的时候,让指针指向整个二维数组的第二行”推展至i,即:
p+i==&a[i];*(p+i)==a[i];
而拉出p本身看,它本身其实就代表着一个一维数组,“[5]"表示一维数组包含5个元素
*(p+2)+3表示a[2][3]地址(2加在p位置里面,3加在一维数组的5个位置里面;【从0加起】)
*(*(p+2)+3))表示a[2][3]的值。
这里很有意思且一石二鸟的地方是:
一来(向外)“(*p)[5]”作为整个*(p+i)系列的首个元素,本身就对应了数组名找的是二维数组的首元素
同时(向内)数组名(*p)[5],本身就被看做(*p)[5]这个一维数组的首(个)元素(p[0])的地址(指针(指向这个地址))
为什么**p不能用:
二维数组名即数组首地址,不是指针的指针。
表面上:
行地址即一维数组指针,而数组名指向行搜索即指针的指针。
但是如果A[3][3],int**p=A;
执行p++时,编译器如何知道长度(列宽:一行有多少列)?
所以,A[3][3]的地址类型,不是简单的“指针的指针”,而是行指针的指针。
而行宽是由定义的数组列数和元素类型所决定的;
例如:int类型就是4*3=12个字节。(数据对齐)
所以A的地址类型应该是int ()[3],而不是int **。
所以应该:int (*p)[3]=A;
其含义为:
p是一个指向(含3个int类型元素的一维数组or行的)指针
其实我们完全可以将一种指针类型强制转为任何其他类型;
那为什么还要区分指针类型呢?
为了实现数据对齐,准确定位。
附: