二维数组a[ ][ ]和(*p)[4]

原文 https://zhuanlan.zhihu.com/p/174091195
1、int[3][4]的各种地址与指针
从前有座山,山里有这么一个二维数组:int a[3][4]

然后我们看这个二维数组,这里很明显可以有一个问题,就是a,*a,&a等等是什么类型?它们有什么区别吗?

具体可以直接看下面的表格,左列为变量名,右列为该变量的类型:
在这里插入图片描述

测试代码如下,主要用了typeinfo头文件里的typeid函数,这个函数或者说操作符是C++为支持RTTI(Run-Time Type Identification)-运行时类型识别而提供的操作符之一,RTTI使程序能够获取由基指针或引用所指向的对象的实际派生类型,即允许“用指向基类的指针或引用来操作对象”的程序能够获取到“这些指针或引用所指对象”的实际派生类型。另外的操作符主要还有dynamic_cast和static_cast,但本文就不展开了:

#include<iostream>
#include <typeinfo>
using namespace std;

int main(){
    int a[3][4] ={{1,2,3,4},
                {1,2,3,4},
                {1,2,3,4},};
    int (*b)[3][4];
    int (*c)[4];
    int *d;
    int e;

    cout<<"&a: "<<&a<<"\tsizeof(&a): "<<sizeof(&a)<<"\ttypeof(&a): "<<typeid(&a).name()<<endl;
    cout<<"a: "<<a<<"\tsizeof(a): "<<sizeof(a)<<"\ttypeof(a): "<<typeid(a).name()<<endl;
    cout<<"&a[0]: "<<&a[0]<<"\tsizeof(&a[0]): "<<sizeof(&a[0])<<"\ttypeof(&a[0]): "<<typeid(&a[0]).name()<<endl;
    cout<<"a[0]: "<<a[0]<<"\tsizeof(a[0]): "<<sizeof(a[0])<<"\ttypeof(a[0]): "<<typeid(a[0]).name()<<endl;
    cout<<"&a[0][0]: "<<&a[0][0]<<"\tsizeof(&a[0][0]): "<<sizeof(&a[0][0])<<"\ttypeof(&a[0][0]): "<<typeid(&a[0][0]).name()<<endl;
    cout<<"a[0][0]: "<<a[0][0]<<"\tsizeof(a[0][0]): "<<sizeof(a[0][0])<<"\ttypeof(a[0][0]): "<<typeid(a[0][0]).name()<<endl;
   
    system("pause");
    return 0;
}

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

A4_i的意思就是类型为含有4个int元素的array,也就是数组类型;

PA4_i的意思就是类型为含有4个int元素的array pointer,也就是数组指针类型;

同理A3_A4_i的意思就是类型为含有3x4个int元素的整形数组类型;

而PA3_A4_i的意思就是类型为含有3x4个int元素的整形数组指针类型。

可以看到,该测试结果是和表格相符的。

但是,这里就有一个新的问题了,一般不是说数组名是该数组首元素的地址吗,怎么这里 int a[3][4] 的 a 不是一个地址(指针)呢?

2、数组的隐式指针转换
先看两行代码:

int (*p)[4] = a;//这里符合对a是数组首元素地址的理解  
p = &a[0]; // 但是这里为什么需要取地址符?  

在第一行代码中,变量a的类型是3x4的数组,但是却可以被用来赋值给p,原因就是因为c++内部自定的隐式转换规则:
在这里插入图片描述

这个隐式转换直观一点理解就是,把数组类型转换为数组首元素指针类型,所以第一行的代码中,a在被用来赋值时,从int [3][4]类型隐式转换成了 int (*)[4] 类型,赋值正确。

而如果在第二行中,如果把代码改为:

 p = a[0];//报错:不能将 "int *" 类型的值分配到 "int (*)[4]" 类型的实体

则会报错:不能将 “int " 类型的值分配到 "int ()[4]” 类型的实体
在这里插入图片描述
原因也是因为隐式指针转换,重复一次,这个隐式转换直观一点理解就是,把数组类型转换为数组首元素指针类型,所以这回 a[0] 本来是 int[4] 类型(查第一部分的表可知),在被用来赋值时,从 int[4] 类型隐式转换成了 int * 类型,赋值失败。

而用了取地址符 & 之后,从第一部分内容的表可知,&a[0] 的类型就是 int (*)[4] ,赋值正确。

因此就可以回答第一部分最后的问题了,一开始a[3][4] 的 a 确实不是一个地址(指针),而是在被用来赋值时,编译器内部会做相应的指针隐式转换,转换后的 a 才是一个指向数组首元素的指针。

3、a[1]是下级数组名字吗?
在很多时候,你可以这样认为,但我想不仅仅如此。

有数组 a[3][4] ,使用a[1]时,运算符 [ ] 相当于对数组名做了偏移操作,将指针(在完成前面所述的隐式转换之后 a 就是一个指针)移到了第二个元素的开头,实际上并没有什么所谓的下级数组名,这不过,为了方便学习,一般可以从这个角度理解。

当然本质上在内存中,也并没有所谓的多维数组,所有变量都只是存在于一个有序编址的内存上,这个多维只是我们将地址一次偏移多少,将这个偏移中取出来的数据看做成了一个一维数组,多个这样的偏移成了二维数组,要继续将其分成更小的偏移量存取,那就是更多维了。在底层硬件实现上,机器才不管你一维还是二维呢。

所以,我并不是想否定 a[1] 是下级数组名这种认知,而是要在这基础至上,更加强调“偏移量”这个观点。

4、char型数组指针
前面讲完了int类型数组的一些指针类型问题,放到char等其它基本类型也是适用的。

还是看代码:

char(*aa)[6];  
char b[6] = "abcde"; 
aa = &b;//这个时候aa只能用 char(*)[6]类型的进行赋值,不然会报错

上面这段代码定义了一个含有六个 char 元素的数组指针 aa ,然后用 同类型的 &b对其赋值,注意不能直接用 aa = b ,因为这样 b 就会被隐式转换为 char * 类型的了,而 aa 是 char(*)[6]类型的,所以会赋值失败。

最后还有一段代码:

char a[4] = {'k','k','k','k'};
char b[4] = {'k','k','k','k'};
char c[4] = {'k','k','k','k'};

char *A[3] ;

A[0] = a;
A[1] = b;
A[2] = c;//A[3] = {a,b,c};

让我们来回顾一下,这时候 A[3] 里面的 a,b,c 的数据类型已经从 char [4] 被隐式转换为 char *了。

(本文完,部分内容参考自网络资料,若有不足与错误,望指正)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值