玩命加载中qwq

数组,数组,数组

  • 2017-06-19
  • 1,770
  • 3
    我们都知道,数组是C语言中最基本、最重要的数据结构之一啦。但是对好多人来说,数组也许有着不为人知的一面呢!先从最简单的讲起:

int a[10];

    于是我们声明了一个数组。这个数组有10个int元素。它们的下标分别从0到9。

int a[10] = { 0 };

    于是我们定义、并初始化了一个数组。这个数组有10个int元素。它们的下标分别从0到9。每个int元素均被初始化为0。

int a[10] = { 1, 2 ,3, 4, 5 };

    在这儿我们定义、并初始化了一个数组。这个数组有10个int元素。它们的下标分别从0到9。元素下标为0~4的元素分别被初始化为正整数1~5,下标为5~9的元素被初始化为默认的0。
    以上是数组的声明、定义、以及初始化。但是这儿有个问题:我们数组里的元素是一个结构体的话,该怎样初始化这个数组呢?

struct tag_abc {
long long x;
int y;
} abc[10] = { 0 };

    在上面的代码中,我们定义了一个数组。这个数组包含10个结构体元素。每个结构体分别拥有一个long long变量和一个int变量。语句末尾的初始化将每个结构体的每个成员变量都初始化为0。也就是说每个结构体中我们有一个long long类型的0和一个int 类型的0。既然可以一并初始化,那我们需要知道怎样分别初始化。我们想把abc[0] 初始化为(1, 2),把abc[1] 初始化为(3, 4)。[当然1和3是long long类型,2和4是int类型。]
    要实现以上功能,我们只需思考一下在初始化结构体变量的时候我们是怎样做的?

struct tag_abc {
long long x;
int y;
} pos = { 1, 2 };

    这样就能把名称为pos的结构体初始化为(1, 2)。
    把这个“小初始化”代入我们的数组初始化中,使其成为数组元素的“局部初始化”。于是得到了:

struct tag_abc {
long long x;
int y;
} abc[10] = { {1, 2}, {3, 4} };

    好啦,现在我们得到了元素0,1的内容分别为(1, 2),(3, 4)的struct tag_abc数组。
    下面我们来看看怎样访问一维数组。如果我们有一个数组:

int a[10];

    来遍历这个数组吧:

int i;
for (i = 0; i < 10; ++i)
{
a[i] = i + 1;
}

    在上面的代码中又有个问题:我们怎样选择存储下标的整数大小?换句话说,用来遍历数组a的索引整数i必须是int类型吗?或者这个问题可以是int [X]; 这个X的取值范围是多少?一般来说,在每台机器下的C语言实现中,一个int变量就足矣充当X了。有些机器上size_t类型比int大, 但是我们也可以用size_t类型的变量作为数组的索引下标。但是size_t是无符号整数:

size_t i;
for (i = 9; i >= 0; --i)
{
a[i] = (int)(i + 1);
}

    只怕以上代码会出现死循环。因为i永远大于等于0。条件 i < 0 永远也不可能实现。如果用某些机器上与size_t大小相等的有符号long long类型代替则又会出现一个问题:因为不是每台机器上的C语言实现size_t都一定等于long long。所以用int做为索引是考虑可移植性的好做法。如果我们要声明一个元素个数是int容不下的数组,那只好用那台机器上与size_t相匹配的变量了。这样做还得看那台机器的C语言实现是否支持。
    我们都知道一维数组与指针之间的关系,那就是:在一维数组 int a[10]; 中,我们可以把a单独拿出来看做是一个指向int类型的指针。并且这个指针a指向数组的第一个元素。于是 *a 就取出了第一个元素的值。 *(a + 0)也是第一个元素的值。*(a + 1)是第二个元素的值。*a + 1 是第一个元素的值+1后的结果(因为取内容运算符优先级高)。于是我们也可以这样遍历数组a:

int i;
for (i = 0; i < 10; ++i)
{
*(a + i) = i + 1;
}

    这样数组的一个冰山之角就水落而出了:数组int a[10]; 是存放在内存中的10个连续的int变量,而且它们的位置由内存的低地址被安排到内存的高地址。
    在80×86汇编中,如果我们将DS寄存器(数据段寄存器)的值设置成某数组的首位元素地址。那么接着我们就可以使用BX寄存器(基地址寄存器)或者SI(原地址索引)、DI(目的地址索引)寄存器做为线性内存空间的数组的下标来使用。举例来说 mov ax, [bx] 就是把内存 bx + ds 处的WORD内容送到AX中去。mov ax, [3+bx] 就是把内存 (ds * 16) + bx + 3 处的WORD内容送到AX中去。同样 mov ax, [bx] 还可以写成 mov ax, 0[bx]。mov ax, [3+bx] 还可以写成 mov ax, 3[bx]。
    于是在C语言中:int a[10]; a[4] 是a的第五个元素。4[a] 也是a的第五个元素。我们再来写一遍a的遍历:

int a[10];
int i;
for (i = 0; i < 9; ++i)
{
i[a] = i + 1;
}

    也许i[a]这种写法很奇怪,但是知道了汇编语言的大致内容后我们也能见怪不怪啦。[思考,将数组a的偶数位元素统一赋值成16。并且使用这种奇怪的寻址方式。a[0] = 16; a[2] = 16; a[4] = 16…]
    既然数组内的元素都是按照内存地址从小到大的顺序依次排放的,那么拥有结构体为元素的数组也无外乎是这样排放的。既然

struct tag_abc {
long long x;
int y;
} pos = { 1, 2 };

    中 1初始化了 long long x,2初始化了int y。那么我们可以在初始化tag_abc数组的时候去掉括号:

struct tag_abc {
long long x;
int y;
} abc[10] = { 1, 2, 3, 4 };

    这样子,初始化的时候,1被转成long long给了abc[0].x;2被转成int给了abc[0].y;3被转成long long给了abc[1].x;4被转成int给了abc[1].y;剩下的按照奇数位初始化为long long类型的0;偶数位初始化为int类型的0。

  • 看完1维数组我们看2维数组。

int a[5][2];

    有着5*2=10个int元素。如果我们把a看成一张表格,那么 a[2][1] 代表表格的第二行第一列,还是第一行第二列?要知道这个我们必须弄清二维数组是如何在内存中存放的。首先我们看int a[X][Y];这句话的意义:a是一个拥有X个元素的数组,这X个元素中的任何一个都是一个包含有Y个int类型的数组。int a[5][2]; 中,a[0]是指向一个int[2]类型的指针。所以它本身的类型是“int (*)[2]”。a[1]也是指向一个int[2]类型的指针。所以它本身的类型也是“int (*)[2]”。这个规律一直沿用到a[4]。
    在a[0]指向的拥有两个int元素的数组里边:*(a[0] + 0)是第一个元素的内容。*(a[0] + 1)是第二个元素的内容。*(a[0] + 0)也就是a[0][0]。*(a[0] + 1)也就是a[0][1]。它们俩的类型显然都是int。

int a[5][2] = { 1, 2, 3 };
printf(“%d\n”, *(((int (*)[2])a)[0]+1));

    的执行结果是2,因为“(int (*)[2])a”的意思是把a强制转换为一个指向一个拥有2个int元素的数组的指针。其实不用转换,a本来就是一个指向一个拥有2个int元素的数组的指针。利用换元法,我们把“(int (*)[2])a”叫做A。接着 (A)[0] 就是 A+0,就是第一行,A的类型是“int (*)[5]”。我们把“(A)[0]”记作B,*(B+1)就是取得第二列的元素内容。同时这个第二列又属于第一行。画成表格数组a是这样:

1 2
3 0
0 0
0 0
0 0

    在内存中a的元素线性排列成这样:

1 2 3 0 0 0 0 0 0 0

    所以根据以上原理,大家估计一下 printf(“%d\n”, a[1][0]); 的执行结果。对了,就是3。
    int a[X][Y]; X是行,Y是列。 a[2][1] 代表第二行第一列。但是我们不能这样简单地记忆int a[X][Y]; X是行,Y是列。原因如下:请看三维数组 int t[2][3][4]; 显然t拥有2*3*4=24个int元素。t是一个“int (*)[3][4]”类型的变量。t[0]是一个“int (*)[4]”类型的变量。t[0][0]是一个“int *”类型的变量。此时如果还是硬要把int a[X][Y][Z}; 理解为行是X,列是[Y][Z]就很难理解a的本意了。因为行是X,列是[Y][Z]很容易与“行是[Y],列是[Z]的[X]张表格。”诸如此类的理解混淆,分不清孰对孰错。还是这样记忆:int a[X][Y][Z]; a[X]是一个指向[X][Y]个int的指针,这样的指针有X个,下标从0到X-1。a[X][Y]是一个指向[Z]个int的指针,这样的指针有X*Y个,X的下标从0到X-1。Y的下标从0到Y-1。四维五维数组以此类推。
    这样的话在int a[2][3][4]; 中,(a + 0) 的类型是“int (*)[3][4]”,可以获取到(a + 1)。*(a + 0) 的类型是“int (*)[4]”,可以获取到*(a + 0) + 2。*(*(a + 0) + 0)) 的类型是“int *”,可以获取到*(*(a + 0) + 3))。*(*(*(a + 0) + 0))) 的类型就是“int”了,是一维int变量。
    那么快速判断一下 int a[2][3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 9被放哪儿了?对了,是a[0][2][0]。

X零.
1234
5678
9000
X壹.
0000
0000
0000
在X零中:
Y零.
1234
Y壹.
5678
Y贰.
9000
在Y零中:
Y零[0] == 1;
Y零[1] == 2;
Y零[2] == 3;
Y零[3] == 4;
在Y贰中:
Y贰[0] == 9;
Y贰[1] == 0;
Y贰[2] == 0;
Y贰[3] == 0;
所以9位于a[零][贰][零]处。

[思考:]
struct tag_abc {
int x;
int y[2];
} abc[2][2][2][2] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ,15, 16 };

    中16位于哪里?是abc[1][1][1][1].x么?
感谢打赏!
支付宝

灌水吐槽区(登录QQ有头像!)

  • SCP-247

    兔子你有个地方错惹
    “在上面的代码中,我们定义了一个数组。这个数组包含10个结构体元素。每个结构体分别拥有一个long long变量和一个int变量。语句末尾的初始化将每个结构体的每个成员变量都初始化为0。也就是说每个结构体中我们有一个long long类型的0和一个int 类型的0。既然可以一并初始化,那我们需要知道怎样分别初始化。我们想把abc[0] 初始化为(1, 2),把abc[0] 初始化为(3, 4)。[当然1和3是long long类型,2和4是int类型。]
    要实现以上功能,我们只需思考一下在初始化结构体变量的时候我们是怎样做的?”
    中的
    “我们想把abc[0] 初始化为(1, 2),把abc[0] 初始化为(3, 4)。[当然1和3是long long类型,2和4是int类型。]”
    应改为
    “我们想把abc[0] 初始化为(1, 2),把abc[1] 初始化为(3, 4)。[当然1和3是long long类型,2和4是int类型。]”

  • 小雨萌萌哒丶

    ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄棒 w

你必须 登录 才能发表灌水吐槽区(登录QQ有头像!).