作者LoganChien (简子翔)
看板b97902HW
标题[计程] 阵列简介 III (微甲爆了心情不好发文害人...
时间Sat Nov 15 02:59:12 2008
前言
之前在〈阵列简介 I〉我们说了阵列的基本意义与操作;在〈阵列简
介 II〉我们说了字串与字串的操作函式。在这一篇文,我们会再去
探讨阵列的其他用法诸如二维阵列、阵列大小运算子(sizeof)。
*
二(多)维阵列 先前我们介绍了一维阵列,你可以把一维阵列想像
成一条数线,在 0 的位置上放一个元素,在 1 的
位置上放另一个元素,...,在 n-1 的位置上放一
个元素。可是如果只有一维阵列,在处理一些资料
的时候就不那麽方便了。例如说,如果我们有一个
2列x3行 矩阵,我们要如何方便地表示它呢?
┌ ┐
│ 1 , 3 , 5 │
│ │
│ 3 , 5 , 7 │
└ ┘
*
阵列大小运算 有时候,为了方便,我们不会在宣告时写死阵列大
小,而是透过阵列的初始值列表(Initialization
List) 让编译器去决定大小,如果我们的程式又突
然需要阵列的大小,我们该怎麽办呢?
int array[] = {1, 2, 3, 4, 5};
int size = ????;
*
阵列的传递 有时候我们的程式要对阵列做一些重复而锁碎的处
理我们会希望把这些程式码写成一个函式。可是我
们要怎麽样让我们的函式接送一个阵列呢?本文会
大略提到这一个问题,不过真得要详细地说要在学
指标之後才能完全的解释。
int array[7] = {1, 2, 3, 4, 5, 6, 7};
int count = count_if_greater(array, 7 /*size*/, 3 /*num*/);
二(多)维阵列
二维阵列的宣告
如果我们要宣告一个二维阵列,和一维阵列不同的部分就仅止於 []
的数目。
左边的 [] 代表的是我们要有多少列(Row),右边的 [] 代
表的是我们要有多少栏(Column),例如:
int array[2][3];
这样就会是一个二维阵列,他会有 2 列 3 栏。这一个阵列会有 6
个元素(Elememt)。这一个阵列的形状如下:
0 1 2 <= 栏索引
┌─────┬─────┬─────┐
0 │12 │893 │985 │
├─────┼─────┼─────┤
1 │187690 │346743 │-2222 │
└─────┴─────┴─────┘
^= 列索引
当然,和一维阵列的时候一样,索引都是从 0 开始编号,所以栏索
引是 0 到 栏数-1 而列索引是 0 到 列数-1。
另外,值得一提得是在 C/C++ 之中,二维阵列是用「
以列为主(Row
Major)」对映到线性位址上。所谓的以列为主是指「同一列的会被放
在一起」,同一列的不同栏排完之後,才会开始排下一列。所以上面
的范例在记忆体的 Layout 大约如下:
位址(Address)
┌────┐
0x00123344 │0C000000│
// array[0][0]
├────┤
0x00123348 │7D030000│
// array[0][1]
├────┤
0x00123352 │D9030000│
// array[0][2]
├────┤
0x00123356 │2ADD0200│
// array[1][0]
├────┤
0x00123360 │774A0500│
// array[1][1]
├────┤
0x00123364 │52F7FFFF│
// array[1][2]
└────┘
※ 结果可能因测试环境而异,此例是在 Little Endian 与
32-bit Integer 环境之中的结果。
在正常的程式码之中,你如果没有特别注意你应该不会(也不用)
注意这一个细节,不过以後我们有几会说到指标的时候,会再提到。
初始化列表
我们可以在宣告阵列的时候,加上初始化列表(Initializer List),
让阵列元素被初始化为我们想要的数值,而非一大堆本来就在记忆体
中毫无意义的数值。我们和一维阵列的时候一样,不过这一次我们用
了
二层的大括号{}。
int array1[2][3] = {{1, 2, 3},
{4, 5, 6}};
0 1 2 <= 栏索引
┌─────┬─────┬─────┐
0 │1 │2 │3 │
├─────┼─────┼─────┤
1 │4 │5 │6 │
└─────┴─────┴─────┘
^= 列索引
当然,和我们在〈阵列简介 I〉中所述的一样,最左边的维度可以省
略,让编译器帮我们算阵列的大小。例如下面的宣告,就可以帮我们
宣告一个 5 列 x 3 栏的二维阵列,每一个元素的值如下所示:
int array2[][3] = {{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{10, 11, 12},
{13, 14, 15}};
0 1 2 <= 栏索引
┌─────┬─────┬─────┐
0 │1 │2 │3 │
├─────┼─────┼─────┤
1 │4 │5 │6 │
├─────┼─────┼─────┤
2 │7 │8 │9 │
├─────┼─────┼─────┤
3 │10 │11 │12 │
├─────┼─────┼─────┤
4 │13 │14 │15 │
└─────┴─────┴─────┘
^= 列索引
存取元素
当然,如果我们要存取特定元素,我们只要用
二次下标运算子(Sub-
script),就可以了。例如我们要写一个九九乘法表到阵列之中:
int nines[10][10];
int i, j;
for (i = 0; i < 10; ++i)
{
for (j = 0; j < 10; ++j)
{
nines[i][j] = i * j;
}
}
我们要印出来就可以用
for (i = 1; i < 10; ++i)
{
for (j = 1; j < 10; ++j)
{
printf("%d\t", nines[i][j]);
}
printf("\n");
}
结果就是:
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
如果我们要用 scanf 我们就可以用:
scanf("%d", &(array[i][j]));
字串阵列
在学过二维阵列之後,我们可以讨论一下字串阵列,首先我们看一下:
字串(String)的本质是字元阵列,所以字串阵列可以说是字元的二维
阵列。所以我们可以用下面的方法来宣告一个字串阵列:
char weeks[][10] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
注意:最右边的维度的大小必需可以容纳最长的字串,以上面的例子
来说,最长的字串是 Wednesday 一共需要 10 个字元。
for (i = 0; i < 7; ++i)
{
printf("%s\n", weeks[i]);
}
(还有一点就是:这一个方法会浪费若干空间,不过解决的方法我们
就不在本文讨论)
多维的阵列
多维阵列和二维阵列唯一的不同就是 [] 的数目,像下面就是一个三
维阵列的例子:
int cubearray[2][2][2];
当然,和上面的规则一样,最左边的维度在有初始化列表的时候可以
省略。
int cubearray[][2][2] = {
{{1,2}, {3,4}},
{{5,6}, {7,8}},
{{9,10}, {11,12}}
};
==>
cubearray[0][0][0] == 1,
cubearray[0][0][1] == 2,
cubearray[0][1][0] == 3,
cubearray[0][1][1] == 4,
cubearray[1][0][0] == 5,
cubearray[1][0][1] == 6,
cubearray[1][1][0] == 7,
cubearray[1][1][1] == 8,
cubearray[2][0][0] == 9,
cubearray[2][0][1] == 10,
cubearray[2][1][0] == 11,
cubearray[2][1][1] == 12
和二维阵列一样,多维阵列也是用「
以列为主(Row Major)」来表示
的,不过因为在多维阵列之中没有「列」的观念,所以在 C99-draft
之中,是以
Row-wise 来定义。所谓的 Row-wise 是指:最右边的维
度必需能以最快速、简单的方法来存取,我们可以把 Row-wise 想像
成
最右边的维度会被排在一起。例如上面的,就会是 [0][0][0],
[0][0][1], [0][1][0], [0][1][1], [1][0][0], ...。笔者就不再
赘述。
阵列大小的运算
在 C 语言中,有一个运算子专门用来计算一个型别、变数在记忆体
中的大小。这一个运算子叫做
sizeof。他可以有一个引数,它的值
就是
引数在记忆体中所占有的空间(bytes)。
例如我们可以用它来取得每一个型别的大小(要耗用多少 bytes 的
记忆体)。例如:sizeof(char) 就是 1bytes,其他的值,可能因环
境而异,以下仅供参考:
sizeof(int) == 4,
sizeof(long) == 4,
sizeof(short) == 2,
sizeof(double) == 8,
sizeof(wchar_t) == 2
我们也可以用 sizeof 来取得一个
复合型别(例如:指标、阵列型别)
的大小:
sizeof(void *) == 4
sizeof(char *) == 4
sizeof(int *) == 4
sizeof(int [4]) == 16 // (sizeof(int) * 4)
我们也可以用 sizeof 来取得
阵列变数 所占的空间:
int array[5];
sizeof(array) == 5 * sizeof(int) == 20
所以我们可以发现如果反过来做,我们就可以知道阵列有多少元素:
int array[] = {1, 2, 3, 4, 5};
int size = sizeof(array) / sizeof(int); /* size == 5 */
而二维阵列我们可以用:
int array[][5] = {
{0, 0, 0, 0, 0},
{1, 1, 1, 1, 1},
{2, 2, 2, 2, 2}
};
int size = sizeof(array) / sizeof(int [5]);
来找出列数,在这里,我们可以把 int[5] 视为一个型别,他的大小
是 sizeof(int) * 5。
阵列在函式之间的传递
想像一下,有一天我们必需写一个程式来求出一串数字之中大於给订
数字的个数。而很不幸的,我们会在程式之中常常用到,此时大家想
必会想到用函式把这一个功能抽取出来,但是我们要怎麽传递阵列呢?
预备知识:在这一部分我无可避免得会谈到
指标,对於不熟悉指标的
同学可能会有些吃力。
指标,你可以想像它是一个路标,用以储放位址,我们可以透过这一
个位址找到记忆体中的特定变数,不过这已经超出本文的范筹,有机
会再谈了。
阵列名字可以被转型为对应的指标型别。这是 C 语言中定义的合法转
型之一。不过要注意的是从阵列转型为指标,
会失去一些资讯,如
sizeof 等,我们稍後会再说明。
int array[] = {1, 2, 3, 4, 5};
int *ptr = array; // 转型为指标
int array2d[][3] = {{1, 2, 3}, {2, 4, 6}};
int (*ptr)[3] = array2d; // 转型为「指向阵列的某一列」的指标,
// 且同一列恰有 3 个元素。这一个我
// 现在没有办法说太多,只能说这和线
// 性定址有关,所以不能直接转为 int*
我们怎麽传递阵列?
更正确地来说,在 C 语言之中,为了效率考量,没有传递阵列这一
种事,只有
传递阵列的指标。说得复杂一点,对於阵列而言,只有
Call-by-Pointer 没有 Call-by-Value。要在函式之间传递阵列,
只能透过指标。我们要
透过指标,
间接地存取在阵列中的元素。
另外,在 C 语言之中,下标运算子(Subscript []),和「计算位址
偏移量并取值」是同一件事。所以我们
可以直接对指标做下标运算。
例如我们可以宣告一个函式原型:
int count_if_greater(int array[], int size, int number);
第一个参数看起来像是在传递阵列,事实上编译器会转换成以下的形
式,所以是传递指标;第二个参数是在传递阵列的大小;第三个参数
是传递我们要比较的数值。
int count_if_greater(int *array, int size, int number);
而我们的函式可以这样实作:
int
count_if_greater(int *array, int size, int number)
{
int reuslt = 0;
int pos = 0;
for (pos = 0; pos < size; ++pos)
{
if (array[pos] > number)
{
result++;
}
}
return result;
}
从这一个函式,我们可以发现:我们可以直接对指标做下标运算,就
像是在操作阵列一样,另外就是我们也要传入阵列的大小。
还有,如果你的函式原型如下,则中括号中的数值可写可不写,因为
被转型之後,都是指标,所以不会有问题。
int count_if_greater(int array[ ], int size, int number);
int count_if_greater(int array[9], int size, int number);
==>
int count_if_greater(int *array, int size, int number);
而我个人是倾向不要写,因为
多写并不会保证你输入的阵列大小一定
会大於该数值。所以为了不要有所误解,我是建议你不用写。
如果是另一个例子,我们要处理二维或多维阵列,此时我们
[] 中的
值只有最左边的可以省,因为转型成指标时,为了正确的计算位址偏
移量,右边的维度仍必需相同。
int array[5][2][3][4];
int (*ptr)[2][3][4] = array;
void func(int array[5][2][3][4]);
void func(int array[ ][2][3][4]);
==>
void func(int (*array)[2][3][4]);
还有要注意,因为所有的参数都是指标,所以上面用 sizeof 来计算
阵列大小的方法会有问题,因为
sizeof(array) 是取得
指标在记忆体
之中占用的记忆体量。所以
sizeof(array)/sizeof(int) 不会是阵列
有多少元素,因此我们的函式要传入阵列的大小。
(错) 元素个数的计算
int
count(int array[]) // 就算是 array[5] 也不行
{
int size;
size = sizeof(array)/sizeof(int); // 错!
size = sizeof(array)/sizeof(*int); // 这也错!
return size;
}
结语
我们的阵列简介的系列文章,到此就结束了。可能还有一些内容还没
有讲,不过因为个人之水平有限,或者困难度过高不适合当做简介的
内容,又或者需要更多的预备知识,就不再赘述,有兴趣者可以寻找
相关的资料。
从三篇简介,我们可以学到
基本阵列的使用、字串操作与相关函式、
二维阵列、多维阵列、sizeof、阵列(指标)的传递。希望能让各位
对阵列的使用有基本的认识。
----------------
想写得东西太多,
能用的时间太少,
站在机会成本的岔路上,
选择左边,我才有机会通古今之变,
选择右边,我才有机会成一家之言,
然而我一直停留在原地,究天人之际,
在左右二条道路之间踌躇不定。
嘘 LoganChien: 原 Po 真得是疯了... 11/15 00:56
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 140.112.241.166
1F:推 rohan21:推微甲爆了心情不好(/‵Д′)/~ ╧╧ 11/15 04:10
2F:推 xflash96:推 11/15 09:03