什么叫二维数组?一维数组给我们的感觉就是一行,现在我们学习一下二维数组
26.1 二维数组的创建和初始化
26.1.1 二维数组的创建
二维数组怎么创建呢?它的写法跟一维数组的区别就是形式上的区别,假设我们要创建一个二维数组,我们该怎么创建?
二维数组其实是有行有列的,它就不是一行,而是多行了,一维数组好像是一行,但是对于二维数组来说不是一行
假如我们要创建一个 int 数组叫 arr,我希望它是3行4列的,所以后边只要写出两个方括号,一个写3,一个写4,表示3行4列,第一个3,表示3行;第二个4,表示4列
如果我们希望这个数组的每个元素放 int,我写个 int 就可以了,int 就是数组每个元素的类型,如图所示:
如果我们看到这,我们写出这样的代码,其实在我们的脑海中应该是这样的,这就是我们假想中一个3行4列的二维数组,而它的每个元素放的都是 int,它的每个元素里边都能放一个 int,所以它的每个元素的类型,都是 int 类型的,之所以叫二维,是因为它有行和列,如图所示:
假设我们创建一个数组叫 ch,我希望它是3行10列的,然后希望这个数组的每个都是 char 类型,这样写就可以了,它是3行10列,每个元素是一个 char 类型,它的每个元素都能放一个字符,如图所示:
当行和列在发生变化,每个元素类型在发生变化的时候,这个数组其实就是在发生变化
26.1.2 二维数组的初始化
初始化的意思就是:创建的同时给赋值
假设有一个二维数组是3行4列,我们现在要对它进行初始化,等号上就是给它赋值,二维数组是3行4列,所以可以放12个元素,我得把所有元素括起来,所以就得把所有的数据放到大括号里边去
一种最普通的初始化方式就是 int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12},这样写的话,我们把数组的元素,全部都抛进去了,第一行放的是1,2,3,4;第二行放的是5,6,7,8;第三行放的是9,10,11,12。即:
{1,2,3,4}
{5,6,7,8}
{9,10,11,12}
如果整体用一个大括号把所有元素括起来的时候,我们就一个一个往里放,最终,把所有元素都放进去就可以了,如图所示:
如果写不完全初始化,我就写了7个,那又是什么呢?
第一行里边放的是1,2,3,4;第二行放的是5,6,7,0;第三行放的是0,0,0,0。我们只放了7个元素,第二行都没放满,当我们给这个数组给的初始化内容,是不够把这个数组放满的时候,这叫不完全初始化。不完全初始化的时候,默认会把剩余的初始化成0(后边补0),即:
{1,2,3,4}
{5,6,7,0}
{0,0,0,0}
如图所示:
当然,补0这对于 int 数组,如果它是字符数组的话,补0,其实相当于补的是 \0
除此之外,二维数组还有什么样的初始化形式呢?
首先,对于二维数组,我们可以把它的一行看成一个一维数组,所以我们就一行一行的初始化,所以第一行初始化上个1,2;第二行给上一个3,4;第三行给上一个4,5
编译的时候,我们会发现:第一行1,2,0,0;第二行3,4,0,0;第三行4,5,0,0。所以, 即:
{1,2,0,0}
{3,4,0,0}
{4,5,0,0}
如图所示:
因此,几个大括号就是几行,大括号里有几个元素就是几列,刚才的 int arr[3][4]里边有3个大括号,说明是初始化3行的
值得注意的是:二维数组行可以省略,但是列不能省略,因为里边包含了3个大括号,说明我们是初始化3行的:第一行1,2,0,0;第二行3,4,0,0;第三行4,5,0,0
最重要的是:你有几行,我是会根据你的初始化情况来确定它是几行的,但是一行有几个元素,这个是不能省略的
26.2 二维数组的使用
对于一维数组来说,用下标来访问的,对于二维数组能不能用下标来访问呢?也是可以的
二维数组是怎么用下标来访问的?其实也很简单,根据语法规定:二维数组我们可以指定到行,它的第一行行号是0,列号也是从0开始的。因此,一维数组的下标从0开始的;二维数组的行是从0开始的,列也是从0开始的
如果我想找到6的位置,它的行是1,列是2,所以它就是第1行第2列的元素,所以,当我要确定一个元素的时候,比如说我就要找到6元素的位置,表示它用的是 arr[1][2],即:行号是1,列号是2,所以只要给了横坐标和纵坐标(也就是行坐标和列坐标),我们就能够找得到,如图所示:
假设我们现在,把这个数组的所有元素都打印出来,那我们就一行一行打印,先把行为0的,列为0,1,2,3的打印;再把行为1的,列为0,1,2,3的打印;先把行为2的,列为0,1,2,3的打印,然后我们定义一下 int i = 0 和 int j = 0,for(i = 0; i < 3; i++) 这个意思是 i 从0开始,i < 3的话最大是2,所以行是0,1,2就给出来了
同理,当行0,1,2给定之后,for(j = 0; j < 4; j++) 这个意思是行是0的时候,j 再从0循环到3,这样的话00,01,02,03就产生了
用 j 产生了这个值之后,我们用 arr[i][j]就找到了这个数组的某一行元素,即:第 i 行,所有下标为 j 的元素,然后打印就行了(第508行代码到第511行代码的循环,其实在打印第 i 行,下标为 j 的元素,所以这一行打印完之后,建议换个行,也就是加上 \n),如图所示:
二维数组怎么使用的?用下标来使用的
下标是怎么来的?通过行从0开始,列从0开始
26.3 二维数组在内存中的存储
在我们的想象中,二维数组是一行完了又一行,但实际上它在内存中就是这样的么?我们只有把二维数组在内存中的存储形式一一打印出来,才能够研究的透
还是刚刚的例子,我们的 i 是从0开始的,然后到3,当 i 是0的时候进去,要循环4次,然后把一行打印了。下一次 i++变成1的时候,又进去又要打印4个,那就是一行一行打印的
所以我们先打印第一行;再打印第二行;再打印第三行,把一行的每个元素的地址都打印出来,打印下标为 i 行 j 列这个元素的地址
为了好看,为了一句话能够看到具体是哪行哪列的元素,我们在%p 的前边加上个 &arr[%d][%d] =,第一个元素是 i,第二个元素是 j,第一个%d 对应的行,第二个%d 对应的列,%p 就是我们这个元素的一个地址,即:printf("arr[%d][%d] = %p\n", i, j, &arr[i][j]),如图所示:
我们可以发现:4C 变成50,C是12,12+4=16,余0进1,所以涨了4,然后50变成54,54变成58,那肯定涨的都是4了,从第4行开始,第4行是arr[0][3],第5行是[1][0],这两个位置就跨行了,跨行了之后,那它们的地址差别又是多少呢?还是4
第4行58变成第5行的5C,C是12,8是8,12和8之间又差的是4,5C变成60也差4,我们仔细往下观察会发现:相邻的这两个之间差的都是4,那为什么差4呢?因为这个数组的每个元素都是一个 int,而一个 int 的大小就是4个 byte
那我们就又可以说明一个问题:二维数组在内存中也是连续存放的,这个连续分为2层,第一层就是每一行内部是连续的,行与行之间跨行也是连续的,即:一行内部连续,跨行也是连续
连续是什么意思呢?其实我们假想中,以为我们的二维数组是这样的,但实际上并不是这样的,二维数组其实真正在内存中存放的时候,应该是这样的,所以才有了我们刚刚地址的打印,每个地址之间差4,如图所示:
二维数组在内存中是连续存放的,这样有什么意义?有两个用途,我们就来说两个用途:
第一个用途:我们之前说为什么行可以省略,但是列不能省略?
因为列是决定了一行有多少个元素的,这个非常重要,刚刚的例子一行有4个元素,我们是确定的,我才能知道第二行放到哪里去,如果一行的长度都不确定,第二行放到哪里去,从哪里开始放,这个就不确定了。因为它在内存中是连续存放的,所以我们必须知道我们的第二行应该从哪里开始,所以就明确的得知道一行有多少个元素,一行的长度是多少,所以,列不能省略
当然,如果我们在内存中放的时候,一行放完之后,再放下一行,只要列确定了,就可以随便放,至于总共有多少行,那我不关心,放完了就行,
第二个用途:
只有我们知道了它在内存中是连续存放的,我们如果能够拿到首地址,我们就可以在向后的空间内随便拿,也就是说,只要拿到第一个元素的地址,所有元素都可以打印出来,我们就 &arr[0][0],我就把它第一行第一列的地址拿出来,第一行第一列元素的地址是个 int 指针,int 元素的地址应该放到 int 指针里边去
所以 int * p = arr[0][0],如果是个 int 指针的话,访问的时候,一次访问是一个 int,for(i = 0; i < 12; i++),因为是连续存放的,所以就直接 <12,把12个元素全部拿出来,p 是个指针,如果解引用,就是访问由 p 指向的那个元素,所以我们就打印 *p
打印完之后,p++,p++就是让 p 指针向后挪动一个 int,因为 int 指针,所以+1会跳过一个 int(++可以指向该元素,*可以拿到该元素),那12次往后,我把12个元素就都拿出来了,如图所示:
二维数组在内存中是怎么存的?是连续存放的,第一行存完了,然后第二行,然后第三行等等
对于二维数组来说,有一个非常重要的点:假设有一个数组是 int arr[3][4],也就是3行4列的数组,我们知道,它在内存中的存储是这样的
左边是第一行;中间是第二行;右边是第三行,然后我在访问它第一行里的每个元素的时候,也就是第一个空格的下标是 arr[0][0],第二个空格的下标是 arr[0][1],然后依次是 arr[0][2],arr[0][3]
中间是第二行,第一个空格的下标是 arr[1][0],然后依次是 arr[1][1],arr[1]2],arr[1][3]
右边是第三行,第一个空格的下标是 arr[2][0],然后依次是 arr[2][1],arr[2]2],arr[2][3],如图所示:
当我们在访问这些元素的时候,我们会发现:访问第一行的每个元素的时候,都是 arr[0]开始,加上[0][1][2][3];而第二行的每个元素访问的时候,用的是 arr[1],加上后面的[0][1][2][3];第三行是 arr[2],加上后面的[0][1][2][3]
如果我们把 arr[0]看出一维数组的时候,那它的数组名就是 arr[0],因为数组名后边加上[0][1][2][3],正好能访问一维数组的每个元素,所以,我们在二维数组里边,如果我们能够把二维数组的每一行数组名找出来,那是非常有意义的
如果我们把二维数组看成一维数组,它的第一个元素就是个一维数组;它的第二个元素也是个一维数组;它的第三个元素也是个一维数组
那第一行作为个一维数组的时候,它的数组名是 arr[0],第一行的一维数组的数组名就是 arr[0];第二行的一维数组的数组名就是 arr[1];第三行的一维数组的数组名就是 arr[2]
因为只有有了数组名,再加上后边的[0][1][2][3],用这个下标才能访问这个数组里的每个元素
所以 arr[0]可以认为是第一行这个一维数组的数组名;arr[1]可以认为是第二行这个一维数组的数组名;arr[2]可以认为是第三行这个一维数组的数组名
因此:二维数组的数组名叫 arr,二维数组的第一行的数组名叫 arr[0],第二行的数组名叫 arr[1],第三行的数组名叫 arr[2]
重中之重★:
1. 对于二维数组,我们可以把它的一行看成一个一维数组
2. 几个大括号就是几行,大括号里有几个元素就是几列