奶奶能看懂的C语言,数组与指针怎么?
摘要:本文以浅显生动的方式讲解了C++中数组与指针的关系,展示了数组的遍历方式、多维数组的本质及范围for的应用,让读者理解数组退化为指针的机制与多维结构的内在逻辑。
在上一篇中,我们讨论了 vector 和迭代器,用以遍历一个有序可变序列。而我们知道,在 vector 之下有一种更加基本的数据类型——数组,它是有序固定大小的序列。实际上,我们所涉及的迭代器(范围 for),在数组中也以某种形式可用。
单维数组
使用数组
奶奶都知道怎么创建数组:
int a[100]={0};//创建数组,然后初始化所有元素为0
int a[100]={0,1,2};//创建,然后初始化前三个元素,后面的元素初始化为默认值 (0)
她也知道数组可以使用下标运算遍历(下标从零开始):
for(int i = 0;i<100;i++){
cout<<a[i]<<endl;
}
但是,数组不能直接赋值给另一个数组,这一点和 vector 不同,需要注意(大小都不一样且固定,怎么复制呢?)。
数组即指针
很好。但是你有没有想过,如果我直接把数组输出,会发生什么?
int a[100]={1,2};
cout<<a<<endl; // 0x7ffc4d858a10
WOW,这个输出是不是有些熟悉?
没错,在指针那一篇里,我们曾经见过。这是十六进制数,表示一个地址。也就是说,我们输出了数组,但是它的表现居然和指针一样,输出了一个对象的地址!
但是,这个地址究竟是什么?指向了哪个对象?
嗯,我们解引用试试:
cout<<*a<<endl; // 1
输出的是1。居然是数组的第一个元素!
也就是说,如果我们直接使用数组,却不指定下标,那么它会被当做一个指向第一个元素的指针。
指针和迭代器
事情从这里开始就变得有趣起来了。实际上,数组在内存中是连续存储的,那么,我们可以用指针运算的方式,来遍历整个数组:
int a[5]={0};
for(int *p=a;p!=a+5;p++){
cout<<*p<<endl;
}
在上面的代码中,我们先创建了指向第一个元素的指针,然后不断将指针向后移动,输出解引用后的对象,直到到达最后一个元素的后一个位置,立即退出。查看下面图片,了解具体过程。
实际上,std 提供了 begin() 和 end(),来获取首个元素,和末尾元素的后一个位置。
#include <iterator> //先引入,再使用
int a[5]={0};
for(int *p=begin(a);p!=end(a);p++){
cout<<*p<<endl;
}
是不是很熟悉?我们曾经提到过,范围 for 语句满足的条件:
begin,end 返回的是一个迭代器/迭代器可以自增
实际上,返回的是一个可运算的指针,也是可行的:
int a[5]={0};
for(int i:a){
cout<<i<<endl;
}
上方的三种形式,都是等价的。也就是说,数组也可以用范围 for 语句(当然也可以设定为引用,然后修改)。
多维数组
更高的维度
之前我们创建的数组,是对象的固定有序序列。那么我们想一想,数组其中的对象能否是一个数组呢?实际上是可以的,这称为多维数组。
int a[100][100]={}; // 全部初始化为默认值 (0)
int b[10][10]={{1,2},{2,3}}; // 初始化一部分
我们都知道我们现在所在的空间是三维的,这个维数,就是指的是有多少根轴,比如上下、左右、前后。而 Excel 表格是二维的,有 X Y 两根轴。因此,从名字上来说,多维数组可以理解为一片 N 维的空间,空间中的每个坐标点,每一个都包含一个独立的元素。
比如说,上面定义了两个二维数组,第一个全部初始化为默认值 0,第二个把第一行第一个数设为 1,第二个数设为 2;第二行第一个数设为 2,第二个数设为 3,其余设定为默认值 0。
cout<<b[0][0]<<endl; // 1
cout<<b[0][1]<<endl; // 2
cout<<b[1][0]<<endl; // 2
cout<<b[1][1]<<endl; // 3
下标运算符是可用的。在二维中,我们把第一个下标称为行,第二个称为列。
这是多维数组含义上的理解。但是其本质是什么呢?
多维数组的本质
我们还得来看看这个初始化:
int b[10][10]={{1,2},{2,3}}; // 初始化一部分
不难发现,这个初始化语句的字面值,是一个数组中嵌套了另外一个数组。
