数组心得(一)

17.1 数组与内存

  17.1.1 数组的内存结构

  17.1.2 数组的内存地址

  17.1.3 数组元素的内存地址

  17.1.4 数组访问越界

17.2 二维数组

  17.2.1 二维数组基本语法

  17.2.2 二维数组初始化

  17.2.3 二维数组的内存结构

  17.2.4 二维数组的内存地址

17.3 二维数组实例

  17.3.1 用二维数组做字模

  17.3.2 二维数组在班级管理程序中应用

17.4 三维和更多维数组

  17.4.1 多维数组的定义与初始化

  17.4.2 多维数组的示例

17.5 数组作为函数的参数

  17.5.1 数组参数默认是传址方式

  17.5.2 可以不指定元素个数

  17.5.3 数组作为函数参数的上机实例

  17.5.4 二维及更多维数组作为函数参数

  17.5.5 函数的返回值类型不能是数组

17.6 sizeof用在数组上

  17.6.1 用sizeof自动计算元素个数

  17.6.2 sizeof对数组参数不可用

 

17.1 数组与内存

变量需要占用内存空间,内存空间有地址。不同数据类型的变量,可能占用不同的内存大小及有不同的内存结构。

以前我们所学都称为“简单数据类型”,如:int,char,float,double,bool。像 char,bool,只占用一个字节,所以我们不去管它的的“结构”,其余如int,float,double占用多个字节,但比较简单,适当的时候我们会去探讨4个字节是如何组成一个整数。

后来我们学习了数组。数组变量占用内存的大小是不定的,因为不同的数组变量除了类型可以不同,还可以拥有不同个数的元素,这两点都影响它的大小。

 

因此,数组是我们第一个要着力研究它的结构的数据类型。和后面我们还要学习的更多数据类型相比,数组的结构还是相当简单的。简单就简单在它的各个元素大小一致,整整齐齐地排列。

 

17.1.1 数组的内存结构

变量需要占用内存空间,内存空间有地址。

 

声明一个整型变量

 

int a;

 

系统会为该变量申请相应大小的空间,一个int类型的变量时,需要占用4个字节的空间,如下图:

也就是说,一个 int 类型的变量,它的内存结构就是 “4个连续的字节”。

 

当我们声明一个数组:int arr[100];

 

我们可以想像,arr数组在内存中占用了100 * sizeof(int) 个字节。

 

现在请大家打开Windows的画笔程序,家画一个数组的内存结构示意图。

 

17.1.2 数组的内存地址

 

一个int类型变量,占用4个字节的内存,其中第一个字节的位置,我们称为该变量的内存地址。

同样,一个数组变量,占用一段连续的内存,其中第一个字节的位置,我们称为该数组变量的内存地址。

 

还记得 & 这个符号吗?通过它我们可以得到指定变量的内存地址。

 

int a;

cout << &a << endl;

 

& 称为“取址符”。如果你有点记不清,可以查看以前的课程。

 

本章第一个需要你特别注意的内容来了:

查看数组变量的地址,不需要使用 & 。下面的话是一个原因也是一个结论,你必须记住。

 

C,C++语言中,对数组变量的操作,就相当于直接对该数组变量的地址的操作。

 

因此,想要查看一个数组变量的地址,代码为:

 

int arr[10];

cout << arr << endl;  //注意,arr之前无需 &

 

现在,请大家打开CB,然后将上面代码写成完整的一个控制台程序,看看输出结果。

 

17.1.3 数组元素的内存地址

 

一个数组变量包含多个连续的元素,每一个元素都是一个普通变量。因此,对就像对待普通变量一样可以通过&来取得地址:

 

//查看数组中第一个元素的地址:

int arr[10];

cout << &arr[0] << endl;

 

例一:

现在,请大家在CB里继续上一小节的代码,要求:用一个for循环,输出数组arr中每一个元素的地址。

如果你已完成,现在来看我的答案。

 

#include <iostream.h>

...

int arr[10];

 

for(int i=0; i<10; i++)

    cout << &arr[i] << endl;

...

cin.get();

 

我们把它和前面输出数组地址的例子结合起来,然后观察输出结果。

 

...

int arr[10];

 

//输出数组的地址:

cout << "数组arr的地址: " << arr << endl;

 

//输出每个元素的地址:

for(int i=0; i<10; i++)

    cout << "元素arr[" <<i <<"]的地址:" << &arr[i] << endl;

 

...

 

输出结果:

 

第一个要注意的的是头两行告诉我们,整个数组变量arr的地址,和第一个元素arr[0],二者的地址完全一样。

事实上,数组和元素,是对同一段内存的两种不同的表达。把这一段内存当成一个整体变量,就是数组,把这段内存分成大小相同的许多小段,就是一个个数组元素。

 

请参看下图:

(分开一段段看是一个个元素,整体看称为一个数组,但二者对应的是同一段内存)

 

第二个要注意的,大家算算相邻的两个元素之间地址差多少?比如 &arr[1] - &arr[0] = 1245028 - 1245024 = 4个字节。这4字节,就是每个数组元素的大小。当然,这里是int类型,所以是4字节,如果是一个charbool 类型的数组,则每个元素的大小是1。

 

根据这两点,我来提几个问题:

 

1、如果知道某个int类型数组的地址是 1245024,请问下标为5的元素的地址是多少?

2、如果知道某个char类型的数组,其下标为4的元素地址为:1012349,请问下标为2的元素地址是多少?

 

由于可通过 sizeof() 操作来取得各类型数据的大小,所以我们可以假设有一数组:

 

T arr[N];   //类型为T,元素个数为N。

 

存在:

      &arr[n] = arr + sizeof(T) * n ;  (0 <= n < N)

或者:

      &arr[n] = arr + sizeof(arr[0]) * n;  (0 <= n < N)

 

 

17.1.4 数组访问越界

 

上一章我们说过“越界”。由于这一问题的重要性,我们需要专门再说一回。

 

越界?越谁的界?当然是内存。一个变量存放在内存里,你想读的是这个变量,结果却读过头了,很可能读到了另一个变量的头上。这就造成了越界。有点像你回家时,走过了头,一头撞入邻居家……后果自付。

 

数组这家伙,大小不定!所以,最容易让程序员走过头。

 

我们通过数组的下标来得到数组内指定索引的元素。这称作对数组的访问。

如果一个数组定义为有n个元素,那么,对这n个元素(0 到 n-1)的访问都合法,如果对这n个元素之外的访问,就是非法的,称为“越界”。

比如,定义一个数组:

 

int arr[10];

 

那么,我们可以访问  arr[0] ~ arr[9] 这10个元素。如果你把下标指定为 10 ,比如:

 

int a = arr[10]; //访问了第11个元素。

 

这就造成了数组访问越界。

 

访问越界会出现什么结果了?

首先,它并不会造成编译错误! 就是说,C,C++的编译器并不判断和指出你的代码“访问越界”了。这将很可怕,也就是说一个明明是错误的东西,就这样“顺利”地通过了编译,就这样不知不觉地,一个BUG,“埋伏”在你的程序里。

更可怕的是,数组访问越界在运行时,它的表现是不定的,有时似乎什么事也没有,程序一直运行(当然,某些错误结果已造成);有时,则是程序一下子崩溃。

不要埋怨编译器不能事先发现这个错误,事实上从理论上编译过程就不可能发现这类错误。也不要认为:“我很聪明,我不会犯这种错误的,明明前面定义了10个元素,我不可能在后面写出访问第11个元素的代码!”。

 

请看下面的代码:

 

int arr[10];

 

for(int i=1; i<=10; i++)

{

   cout << arr[i];

}

 

它就越界了看出原因了吗?

 

 

说上一章的成绩查询。我们让用户输入学生编号,然后查该学生的成绩。如果代码是这样:

 

int cj[100]; 

...

 

//让用户输入学生编号,设现实中学生编号由1开始:

cout << "请输入学生编号(在1~100之间):"

int i;

cin >> i;

 

//输出对应学生的成绩:

cout << cj[i-1];

 

这段代码看上去没有什么逻辑错误啊。可是,某些用户会造成它出错。听话的用户会乖乖地输入1到100之间数字。而调皮的用户呢?可能会输入101,甚至是-1 —— 我向来就是这种用户 ——这样程序就会去尝试输出:cj[100] cj[-2]

解决方法是什么?这里有一个简单,只要多写几个字:

 

...

cout << "请输入学生编号(在1~100之间 如果不输入这个范围之内数,计算机将爆炸!):"

int i;

cin >> i;

...

 

系主任在使用你的这个程序时,十个指头一定在不停地颤抖……

理智的作法还是让我们程序员来负起这个责任吧,我们需要在输出时,做一个判断,发现用户输入了不在编号范围之内的数,则不输出。正确答案请看上章。

 

为什么数组访问越界会造成莫名其妙的错误? 前面一节我们讲到数组占用了一段连续的内存空间。然后,我们可以通过指定数组下标来访问这块内存里的不同位置。因此,当你的下标过大时,访问到的内存,就不再是这个数组“份内”的内存。你访问的,将是其它变量的内存了。 前面不是说数组就像一排的宿舍吗?假设有5间,你住在第2间;如果你晚上喝多了,回来时进错了房间,只要你进的还是这5间,那倒不会有大事,可是若是你“越界”了。竟然一头撞入第6间……这第6间会是什么?很可能它是走廊的尽头,结果你一头掉下楼,这在生活中是不幸,可对于程序倒是好事了,因为错误很直接(类似直接死机),你很容易发现。可是,如果第6间是??据我所知,第6间可能是小便处,也可能是女生宿舍。

 

17.2 二维数组

 

事实要开始变得复杂。

生活中,有很多事物,仅仅用一维数组,将无法恰当地被表示。还是说学生成绩管理吧。一个班级30个学员,你把他们编成1到30号,这很好。但现在有两个班级要管理怎么办?人家每个班级都自有自的编号,比如一班学生编是1~30;二班的学生也是1~30。你说,不行,要进行计算机管理,你们两班学员的编号要混在一起,从1号编到60号。

 

另外一种情况,仍然只有一个班级30人。但这回他们站到了操场,他们要做广播体操,排成5行6列。这时所有老师都不管学员的编号了,老师会这样喊:“第2排第4个同学,就说你啦!踢错脚了!”。假设我们的校长大人要坐在校长室里,通过一个装有监视器的电脑查看全校学员做广播体操,这时,我们也需要一个多维数组。

 

17.2.1 二维数组基本语法

 

语法:定义一个二维数组。

 

数据类型  数组名[第二维大小][第一维大小];

 

举例:

 

int arr[5][6];  //注意,以分号结束。

 

这就是操场上那个“5行6列的学生阵”。当然,哪个是行哪个列凭你的习惯。如果数人头时,喜欢一列一列地数,那你也可以当成它是“5列6行”——台湾人好像有这怪僻——我们还是把它看成5行6列吧。

 

现在:

 

第一排第一个学员是哪个?答:arr[0][0]

第二排第三个学员是?答:arr[1][2]

 

也不并不困难,对不?惟一别扭的其实还是那个老问题:现实上很多东西都是从1开始计数,而在C里,总是要从0开始计数。

 

接下来,校长说,第一排的全体做得很好啊,他们的广播体操得分全部加上5分!程序如何写?答:

 

for(int col=0; col<6; col++)

{

   arr[0][col] += 5;

}

 

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值