数组名到底是什么

前言

为什么会想到这个问题呢?之前学习 C 语言的时候,在数组与指针那里,并没有思考数组指针和指针数组到底是什么?一维数组名、二维数组名到底是什么?在看 C++ 的时候,书里讲到数组时说:“一个数组 int a[5],&a+1 的结果是增加20”,当时就在想,这个 &a 是什么呢?书里讲到函数的时候说:“如果形参是一个二维数组,函数原型该怎么定义形参呢?int str[][2]”,为什么不能省略列的原因,我在学习笔记(四)里想明白了。那么问题来了,二维数组名又是什么,它怎么不是二级指针?如果是的话,函数形参为什么不能是 str[][]?

思考

在聊这个问题的时候,先回忆回忆什么是数组指针,什么又是指针数组。

int (*ptr1) [3];  //数组指针
int *ptr2 [3];  // 指针数组

根据书上的定义,ptr1 是一个数组指针,ptr2 是一个指针数组,指针数组好理解。顾名思义,ptr2 首先是一个数组,它的每一个元素都是一个指针。ptr1 是一个数组指针,同样顾名思义,ptr1 得是一个指针,不过它指向一个数组,也就是一大块内存空间。那么数组指针特殊在哪呢?

#include <iostream>
using namespace std;

int main(void)
{
	int(*ptr1)[3] = nullptr;
	int* ptr = nullptr;
	
	cout << sizeof ptr1 << endl;
	cout << sizeof ptr << endl;

    return 0;
}

 说明数组指针跟普通的指针一样,在我的电脑上都是占 8 字节;然而

#include <iostream>
using namespace std;

int main(void)
{
	int(*ptr1)[3] = nullptr;
	int* ptr = nullptr;
	
	ptr = ptr1;

    return 0;
}

 也就是说数组指针跟一般的指针不是相同类型的,ptr1 是 int (*)[3] 类型的。

下面开始谈谈数组名。首先是一维数组(int a[3]),a 可以当一个指针常量使用,它只能指向数组的第一个元素。它可以直接赋值给一个跟数组同类型的指针(ptr2[1] = a),当 “sizeof 数组名” 的时候,得到的是整个数组的大小,但是它并不是“这个数组的指针”,打双引号的,也就是说一维数组名它不是一个数组指针,它只是指向数组的第一个元素,而数组的特性,数组的地址和首元素的地址相同,“sizeof 数组名” 的时候解释成了整个数组。

那么指向这个数组的数组指针是什么呢?答案是 “&a”。这也就是为什么书上说(&a+1)的值是加整个数组的字节数,因为 &a 的类型是数组指针。

#include <iostream>
using namespace std;

int main(void)
{
	int a[3] = { 1, 2, 3 };

	cout << a << endl;
	cout << a + 1 << endl;
	cout << &a << endl;
	cout << &a + 1 << endl;

    return 0;
}

 

 a+1 增加4字节,&a+1 增加12字节,也就是一维数组名是一个数值指针,它只指向数组第一个元素;而 “&数组名”是这个数组的数组指针,由于数组的特性,它们的值是相同的。

说完一维数组,来说说二维数组:int b[2][2] 。如果我们要显示该数组的第一个元素,可以这么做

#include <iostream>
using namespace std;

int main(void)
{		
	int b[2][2] = {1,2,3,4};
	
	cout << b[0][0] << endl;
	cout << **b << endl;
    
    return 0;
}

 这个时候就会产生疑问,既然二维数组名可以两次解引用,那是不是就说明二维数组名其实是一个二级指针呢?

#include <iostream>
using namespace std;

int main(void)
{		
	int b[2][2] = {1,2,3,4};
	int** p = nullptr;
	
	p = b;

    return 0;
}

 编译报错,编译器告诉我们二维数组名它是一个数组指针!这也就是为什么在构建函数的时候,二维数组作为形参时要定义一个数组指针的类型!

那么到底什么情况下数组名会是二级指针呢?答案是“指针数组的情况下”:

#include <iostream>
using namespace std;

int main(void)
{		
	int* ptr2[3] = { nullptr };
	int** p = nullptr;
	
	p = ptr2;

    return 0;
}

编译是可以通过的。其实也很好理解,ptr2 是一个指针,它指向数组第一个元素,而该元素是一个指针,也就是说 ptr2 是一个指向指针的指针,符合二级指针的定义。

不过我在刚钻进这个问题的时候是想不明白的,在网上找到个帖子:

二维数组和二级指针(真的没什么关系) - 青儿哥哥 - 博客园

当时没有理解数组指针这个概念,所以看帖子上的程序时满是困惑:

#include <iostream>
using namespace std;

int main(void)
{		
	int a[2][2] = {1,2,3,4};
	int** p;

	p = (int **) a;
	
	cout << *p << endl;
	cout << a[0] << endl;
	cout << "指针的长度为:" << sizeof p << endl;
	cout << "值为:" << (int)(*p) << endl;
	p++;
	cout << "值为:" << (int)(*p) << endl;

	return 0;
}

 经过前面的铺垫,我们知道了 a 是数组指针,所以它要强制转换才能赋值给二级指针 p,由于数组的特性,a 的值就是该数组的地址,也是 a[0][0] 的地址。但是,在我的电脑上,int 占4字节,而指针占8字节。p 作为二级指针,它的一次解引用 *p 是一级指针,所以它占了 a[0][0] 和 a[0][1]两块内存,而根据字节序,显示的时候是 a[0][1]a[0][0],也就是 cout << *p 得到了0000000200000001 的原因。而强制转换 (int)*p 时,截断了前4个字节,所以输出值1;p++时,p是指针,所以一次加8字节,也就是跳过两个int元素,因此输出值3。

总结

数组名就是指向数组第一个元素的指针常量。数组指针解引用,就可以把它当作所指向的数组的数组名使用。

一维数组名是一个一级指针常量,它只能指向数组的第一个元素,“&一维数组名”  是该数组的数组指针。数组指针和一般的指针不同,虽然都占8字节,但是类型不同,不能直接赋值,需要强制转换。

二维数组的数组名也是该第一个元素的指针常量,只不过这个元素是个数组,所以数组名就是数组指针常量。这也就是为什么实参是二维数组的函数,原型不能定义成 int str[][]  的原因,它不是二级指针!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值