二维数组、指针数组、字符串二维数组
如何使用指针访问二维数组?
先来看看在一维数组中怎样使用指针访问数组:
一维数组中 a[i] 中的 a 代表了本数组的首地址,相当于 &a[0]。
因此 *a 就等于 a[0]。
那么对 a 加 1,就可以访问下一位:
*(a+1) 就等于 a[1]。
可以看出,指针与数组的关系非常密切。好像两个类型没有什么差别,只是换个形式来表达而已。
其实这是因为数组在内存中是一块连续的空间。这时候使用指针形式和数组形式来访问数组并没有差别。
那么在二维数组中如何用指针访问呢?
我们首先来看看二维数组的构成以及如何访问:
与一维数组类似的是,a[i][j] 中的 a 也代表了该数组在内存中的首地址。a 相当于 &a[0][0]。
可以将二维数组看作是由两个一维数组构成的。假设有 a[2][2] 这样一个二维数组,可以这么理解它:a 由 a[0],a[1],a[2] 构成,而 a[0] 又由 a[0][0],a[0][1],a[0][2] 构成,a[1] 由 a[1][0],a[1][1],a[1][2] 构成,a[2] 由 a[2][0],a[2][1],a[2][2] 构成。
所以如果将二维数组这样(将序号用 a[i] 表示)来描述,会更加清晰:
[0] | [1] | [2] | |
---|---|---|---|
a[0] | 5 | 2 | 1 |
a[1] | 1 | 3 | 1 |
a[2] | 4 | 1 | 4 |
我们可以清晰地看到每一个 a[i][j] 都代表了谁:
- a[0],a[1],a[2] 分别代表了那一整行,比如 a[1] 代表了 “1 3 1” 这个一维数组。
- 加上列标,可以访问那一行中的某一个元素,比如 a[0][0] 就是 5。
知道二维数组的概念并会使用二维数组的方式来访问数组后,我们就可以来谈谈如何用指针访问二维数组了。
a 由 a[0],a[1],a[2] 构成,而 a[0] 又由 a[0][1],a[0][1],a[0][2] 构成
两个一维数组构成了一个二维数组。a 是一个二维数组,a[i] 是一个一维数组。既然 a[i] 是一个数组,那么数组名就是这个数组的首地址。比如,a[0] 就等同于 &a[0][0]。
地址一旦出现,指针就可以登场了。
a 是一个数组名,它有三个元素:a[0],a[1],a[2]。所以:
*a 等于 a[0], *(a+1) 等于 a[1], *(a+2) 等于 a[2]。
a[0], a[1], a[2] 又都是各个一维数组的首地址。比如 a[0] 就等同于 &a[0][0],就是说 *a[0] = a[0][0]。
再结合上面的 *a 等于 a[0],可以得出:**a 等于 a[0][0]。
以 a[2][2] 为例,指针与数组的等价关系如下表所示:
数组元素 | 指针访问 |
---|---|
a[0][0] | **a |
a[0][1] | *(*a+1) |
a[0][2] | *(*a+2) |
a[1][0] | **(a+1) |
a[1][1] | *(*(a+1)+1) |
a[1][2] | *(*(a+1)+2) |
a[2][0] | **(a+2) |
a[2][1] | *(*(a+2)+1) |
a[2][2] | *(*(a+2)+2) |
虽然看起来很繁琐,但是可以总结成一句话:
二维数组 a[i][j] 中的 a[i] 是一个一维数组名,也就是一个地址量。
再记住二维数组的描述图:
[0] | [1] | [2] | |
---|---|---|---|
a[0] | |||
a[1] | |||
a[2] |
记住这两个点,就可以清晰地明确二维数组的概念以及如何用指针访问二维数组中的元素了。
字符串二维数组
我们知道字符串是一个一维数组,那么多个字符串就可以用字符串数组来表达。
现在我们来定义并初始化一个二维数组:
char s[3][5] = {"I", "love", "you"};
上面定义了一个 3 行 5 列的二维数组。在内存中存放格式如下:
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
s[0] | I | \0 | |||
s[1] | l | o | v | e | \0 |
s[2] | y | o | u | \0 |
如果使用指针,可以这么初始化:
char *s[] = "I", "love", "you"};
在内存中存放的格式与使用二维数组初始化有些不同:
.... 0 1 2 3 4
s[0] I \0
s[1] l o v e \0
s[2] y o u \0
可以看出,借助指针对二维数组初始化时,省去了不必要的空间,对内存空间的利用率更高。
访问字符串数组:
通常对字符串的处理,传入的的参数都是字符串的首地址,也就是字符串名。
比如:
char s[80]; // 定义一个长度为 80 的字符串 s
char t[80]; // 定义一个长度为 80 的字符串 t
gets (s); // 读入一个字符串
pus (s); // 输出一个字符串
strcpy (s, t); // 将字符串 t 拷贝到 字符串 s 中
其中的 s 都是该字符串的首地址。
上小节里面我们提到:
二维数组 a[i][j] 中的 a[i] 是一个一维数组名,也就是一个地址量。
所以这里上面的字符串数组 s 中的 s[1] 就是一个一维数组,是一个地址量,代表了 “love” 这个字符串数组,是它的首地址。
所以如果我们想输出 “love” 这个字符串,可以这样写:
puts (s[1]);
如果你想用指针访问,可以这样写:
puts (*(s+1));
这样会比数组的形式复杂点。
总结
文章主要解释了二维数组的构成、如何用指针访问数组以及关于字符串数组的一点问题。
希望上面的解释可以让你对二维数组有一个视觉上的印象(那个描述图)。因为二维数组构成上的一些特点,我们可以借助指针来访问它,虽然相比使用数组形式访问繁琐点,但通过这个过程可以让我们对二维数组的构成更加清晰明了。
在字符串数组的初始化上显然更建议用指针数组来进行初始化,这样能更好地利用内存空间。
但在访问字符串数组(或者其他二维数组)上,用数组形式更加简洁、清晰。