- WinCE Security...
- xdebug配置说明
- VC++ 获取文件的创建、修...
- ASP进度条
- 简单代理服务器C代码实现(S...
- 程序设计竞赛试题选(02)
- 如何在ASP程序中打印Acc...
- UTF-8和16进制区间
- ASP实用技巧:强制刷新和判...
- 运行中程序删除自己的方法
- asp提高首页性能的一个技巧
- [J2EE]J2EE 应用服务器技术
- VB变量命名规范
- C语言常见错误小结
- (摘自网络)如何在IIS中调...
第十七章 数组(二)
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字节,如果是一个char或bool 类型的数组,则每个元素的大小是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;
}
对了,这里我没有用 i 来作循环的增量,而是用col。因为col在英语里表示“列”,这样更直观对不?下面要用到行,则用row。
广播操做到“跳跃运动”了,校长大人在办公室蹦了两下,感觉自已青春依旧,大为开心,决定给所有学员都加1分,程序如何写?答:
for(int row = 0; row < 5; row++)
{
for(int col = 0; col < 6; col++)
{
arr[row][col] += 1;
}
}
看明白了吗?在二维数组,要确定一个元素,必须使用两个下标。
另外,这个例子也演示了如何遍历一个二维数组:使用双层循环。第一层循环让row 从 0到 4, 用于遍历每一行;col从0到5,遍历每一行中的每一列。
(遍历:访问某一集合中的每一个元素的过程)
大家把这两个程序都实际试一试.
17.2.2 二维数组初始化
一维数组可以定义时初始化:
int arr[] = {0,1,2,3,4};
二维数组也可以:
int arr[5][6] =
{
{ 0, 1, 2, 3, 4, 5},
{10,11,12,13,14,15},
{20,21,22,23,24,25},
{30,31,32,33,34,35},
{40,41,42,43,44,45},
}; //注意,同样以分号结束
初始化二维数组使用了两层{},内层初始化第一维,每个内层之间用逗号分隔。
例二:
我们可以把这个数组通过双层循环输出:
for(int row = 0; row < 5; row++)
{
for(int col = 0; col < 6; col++)
{
cout << arr[row][col] << endl;
}
}
这段代码会把二维数组arr中的所有元素(5*6=30个),一行一个地,一古脑地输出,并不适于我们了解它的二维结构。我们在输出上做些修饰:
for(int row = 0; row < 5; row++)
{
cout << "第" << row + 1 << "行: "
for(int col = 0; col < 6; col++)
{
cout << arr[row][col] << ","; //同一行的元素用逗号分开
}
cout << endl; //换行
}
请大家分别上机试验这两段代码,对比输出结果,明白二维数组中各元素次序。下面是完整程序中,后一段代码的输出:
现在说初始化时,如何省略指定二维数组的大小。
回忆一维数组的情况:
int arr[] = {0,1,2,3,4};
代码中没有明显地指出arr的大小,但编译器将根据我们对该数组初始化数据,倒推出该数组大小应为5。
那么,二维数组是否也可以不指定大小呢?比如:
int arr[][] =
{
{1,2,3},
{4,5,6}
}; //ERROR!
答案是:对了一半……所以还是错,这样定义一个二维数组,编译器不会放过。正确的作法是:
必须指定第二维的大小,而可以不指定第二维的大小,如:
int arr[][3] =
{
{1,2,3},
{4,5,6}
};
编译器可以根据初始化元素的个数,及低维的尺寸,来推算出第二维大小应为:6 / 3 = 2。但是,很可惜,你不能反过来这样:
int arr[2][] =
{
{1,2,3},
{4,5,6}
}; //ERROR! 不能不指定低维尺寸。
事实上,低维的花括号是写给人看的,只要指定低维的尺寸,编译器甚至允许你这么初始化一个二维数组:
int arr[][3] = {1,2,3,4,5,6}; //看上去像在初始一维数组?其实是二维的。
看上去像在初始一维数组?其实是二维的。 为什么可以这样?我们下面来说说二维数组的内存结构。
17.2.3 二维数组的内存结构
从逻辑上讲,一维数组像一个队列,二维数组像一个方阵,或平面:
一维数组是“一长串” |
这是一个二维数组,4行3列,像一个4*3的平面 |
一维数组的逻辑结构和它在内存里的实际位置相当一致的。但到了二维数组,我们应该想,在内存真的是排成一个“平面”吗?这不可能。内存是一种物理设备,它的地址排列是固定的线性结构,它不可能因为我们写程序中定义了一个二维数组,就把自已的某一段地址空间重新排成一个“平面”。后面我们还要学更高维数组,比如三维数组。三维数组的逻辑结构像一个立方体。你家里有“魔方”吗?拿出来看看,你就会明白内存更不可能把自已排出一个立方体。
结论是:内存必须仍然用直线的结构,来表达一个二维数组。
比如有一个二维数组:
char arr[3][2] = //一个3行2列的二维数组。
{
{'1','2'},
{'3','4'},
{'5','6'}
};
它的内存结构应为:
下标 | 内存地址
(仅用示意) |
元素值 |
[0][0] | 100001 | '1' |
[0][1] | 100002 | '2' |
[1][0] | 100003 | '3' |
[1][1] | 100004 | '4' |
[2][0] | 100005 | '5' |
[2][1] | 100006 | '6' |
(二维数组 char arr[3][2] 的内存结构:每个元素之间的地址仍然连续)
也就是说,二维数组中的所有元素,存放在内存里时,它们的内存地址仍然是连续的。假如另有一个一维数组:
char arr[6] = {'1','2','3','4','5','6'}; //一维数组
这个一维数组的内存结构:
下标 | 内存地址
(仅用示意) |
元素值 |
[0] | 100001 | '1' |
[1] | 100002 | '2' |
[2] | 100003 | '3' |
[3] | 100004 | '4' |
[4] | 100005 | '5' |
[5] | 100006 | '6' |
(一维数组 char arr[3][2] 的内存结构)
你猜到我想说什么了吗?请对比这两个表:一个有2*3或3*2的二维数组,和一个有6个元素的同类型一维数组,它们的内存结构完全一样。所以前面我们说如此定义并初始化一个二维数组:
int arr[][3] = {1,2,3,4,5,6};
也是正确的,只是对于程序员来说有些不直观,但编译器看到的都一样:都是那段同内存中的数据。不一样的是前面的语法。对于一维数组:
int arr[] = {1,2,3,4,5,6};
红色部分告诉编译器,这是一个一维数组。对于二维数组:
int arr[][3] = {1,2,3,4,5,6};
红色部分告诉编译器,这是一个二维数组,并且低维尺寸为3个,也就是要按每3个元素分出一行。C++的语法规定,编译器首先查看低维大小,所以我们若没有指明低维大小,则编译器立即报错,停止干活。因此,定义:
int arr[2][] = {1,2,3,4,5,6}; 是一种错误。
17.2.4 二维数组的内存地址
了解了二维数组的内存结构,我们再来说说几个关于二维数组地址问题,会有些绕,但并不难。嗯,先来做一个智力测试。
以下图形中包含几个三角形?
正确答案是:3个。想必没有人答不出。我们要说的是 :这三个三角形中,两个小三角和一个大三角重叠着,因此若计算面积,则面积并非三个三角形的和,而是两个小三角或一个大三角的面积。
这个问题我们在一维数组时已经碰到过:一个数组本身可称为一个变量,而它包含的各个元素也都是一个个变量,但它们占用的内存是重叠的。
二维数组本身也是一个变量,并且也直接代表该数组的地址,我们要得到一个二维数组变量的地址,同样不需要取址符:&。
int arr[2][3] = {1,2,3,4,5,6};
//输出整个二维数组的地址。
cout << arr;
同样,我们也可以得到每个元素的地址,不过需要使用取址符:
//输出第一个元素(第0行第0列)的地址:
cout << &arr[0][0] << endl;
//输出第2行第3列的元素地址:
cout << &arr[1][2] << endl;
除此之外,我们还可以按“行”来输出元素地址,不需要使用取址符:
//输出第一行元素的起始地址:
cout << arr[0] << endl;
//输出第二行元素的起始地址:
cout << arr[1] << endl;
上图表明:arr, arr[0], &arr[0][0] 都指向了同一内存地址。即: arr == arr[0] == &arr[0][0]。
另外: arr[1] == &arr[1][0] 及 arr[2] == &arr[2][0]。
我们可以有这些推论: 二维数组中的每一行,相当于一个一维数组。或者说,一维数组是由多个简单变量组成,而二维数组是由多个一维数组组成。
示意图:
例子:
int arr[2][3];
则: arr[i][j] 相当于一个普通int变量。而 arr[i] 相当于一个一维数组。
现在,我还是来提问两个问题:
问题一:
有一数组 char arr[3][4];
已知 arr 中第一个元素(arr[0][0])的地址为:10000,请问 &arr[2][1] 的值为?
解答:先要知道arr[1][1]是数组arr中的第几个元素? 数组arr共3行,每行4列,而arr[2][1]是位于第3行第2列,所以它是第: 2 * 4 + 2 = 10,即第10个元素。
这样就计算出来,第1个元素地址是10000,则第10个元素地址: 10000 + (10 - 1) * sizeof(char) = 10009。
问题二:
如果上题中的数组为: int arr[3][4];其余不变,请问该如何计算?
答案:10000 + (10 - 1) * sizeof(int) = 10036。
17.3 二维数组实例
是不是前面的内容让你有些发晕。知识重在应用。我们还是来多操练几个二维数组的例子吧。但是,等用得多了,用得熟了,我希望大家回头再看前面的那些内容。
17.3.1 用二维数组做字模
例三: 字模程序。
手机屏幕是如何显示英文字母或汉字的?这个小程序将要从原理上模拟这个过程。
手机屏幕采用的字体称为“点阵”字体。所以“点阵”,就是用一个个小点,通过“布阵”,组成一个字形。而这些点阵数据,就是一个二维数组中的元素。不同的手机,点阵的大小也不同。如果不支持中文,则最小只需7*7;但若是要支持汉字,则应不小于9*9,否则许多汉字会缺横少竖。采用大点阵字体,则手机屏幕要么是面积更大,要么是分辨率更高(同一面积内可以显示更多点);并且手机的内部存储器也要更多。由于汉字数量众多,不像英文主要只有26个字母;所以支持汉字的手机,比只能显示英文字手机,其所需存储器自然要多出一个很大的数量级。
下面举例英文字母“A"的点阵,为了看的方便,我们用*来代替小黑点,并且打上了表格。我们使用最小的7*7点阵:
* | ||||||
* | * | |||||
* | * | * | * | * | ||
* | * | |||||
对于这样一个点阵,对应一个二维数组为:
int A[7][7] =
{
{0,0,0,0,0,0,0},
{0,0,0,1,0,0,0},
{0,0,1,0,1,0,0},
{0,1,1,1,1,1,0},
{1,0,0,0,0,0,1},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,0},
};
程序要在屏幕上打出“A"时,则只需遍历该数组,然后在元素值为0的地方,打出空格,在元素值为1的地方,打出小点即可。当然,在我们的模拟程序里,我们打出星号。
所有这些数组,都需要事先写到手机的固定存储器中,这些数据就称为“字模”。
对于“A"的例子,打印时的代码如下:
for(int row = 0;row < 7; row++)
{
for(int col = 0; col < 7; col++)
{
if(A[row][col] == 0)
cout << ' ';
else
cout << '*';
}
//别忘了换行:
cout << endl;
}
结果如:
大家小时候有没刻过印章?哎!大概80年代出生的人是不会有过这种游戏了。印章分“阴文”和“阳文”。如果把上面的程序稍做修改,即在元素值为0的地方打出“*”,而在元素值为1的地方打出空格,那么输出结果就是“阴文”了。大家不妨试试。
例四: 躺着的“A”。
同样使用例三的二维数组数据:
int A[7][7] =
{
{0,0,0,0,0,0,0},
{0,0,0,1,0,0,0},
{0,0,1,0,1,0,0},
{0,1,1,1,1,1,0},
{1,0,0,0,0,0,1},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,0},
};
请改动例三的程序,但不允许改变数组A的元素值,使之打印出一个躺着的“A”。下面是输出结果:
请大家想想如何实现,具体代码请见课程配套代码源文件。
17.3.2 二维数组在班级管理程序中应用
例五: 多个班级的成绩管理
以前我们做过单个班级,或整个学校的成绩,那时都是将所有学生进行统一编号。事实上,不同的班级需要各自独立的编号。
比如初一年段有4个班级,每个班级最多40人。那么很直观地,该成绩数据对应于这样一个二维数组:
int cj[4][40];
在这里,数组的高维(第二维)和低维(第一维)具备了现实意义。例中,4代表4个班级,40代表每个班级中最多有40个学生。因此低维代表班级中学生的编号,高维代表班级的编号。这和现实的逻辑是对应的:现实中,我们也认为班级的编号应当比学员的编号高一级,对不?你向别人介绍说:“我是2班的24号”,而不是“我是24号的2班”。
一个可以管理多个班级的学生成绩的程序,涉及到方方面面,本例的重点仅在于:请掌握如何将现实中的具有高低维信息,用二维数组表达出来。并不是所有具有二维信息的现实数据,都需要明确地区分高低维,比如一个长方形,究竟”长“算”高维还是“宽”算高维?这就无所谓了。但另外一些二维数据,注意到它们的高低维区分,可以更直观地写出程序。
闲话少说,现在问,2班24号的成绩是哪个?你应该回答: cj[1][23]; ——最后一次提醒,C++中的数组下标从0开始,无论是一维二维还是更多维的数组,所以2班24号对应的是下标是1和23。
我们要实现以下管理:
1、录入成绩,用户输入班级编号,然后输入该班每个学员的成绩;
2、清空成绩,用户输入班级编号,程序将该班学员的成绩都置为0;
3、输出成绩,用户输入班级编号,程序将该班学员成绩按每行10个,输出到屏幕;
4、查询成绩,用户输入班级编号和学员编号,程序在屏幕上打出该学员成绩。
5、统计成绩,用户输入班级编号,程序输出该班级合计成绩和平均成绩。
0、退出。
看上去,这是一个稍微大点的程序了。
我们已经学过函数,所以上面的四个功能都各用一个函数来实现。另外,“让用户输入班级编号”等动作,我们也分别写成独立的函数。
四个功能中,都需要对各班级学员成绩进行处理,所以我们定义一个全局的二维数组。
下面我们一步一步实现。
第一步:定义全局二维数组,加入基本的头文件。
第一步的操作结果应是这样(黑色部分为需要加入的代码):
//--------------------------------------------------------------------------
//支持多班级的成绩管理系统
#pragma hdrstop
#include <iostream.h>
//---------------------------------------------------------------------------
#define CLASS_COUNT 4 //4个班级
#define CLASS_STUDENT_COUNT 40 //每班最多40个学员
//定义一个全局二维数组变量,用于存储多班成绩:
int cj[CLASS_COUNT][CLASS_STUDENT_COUNT]; //提示:全局变量会被自动初始化为全0。
//所以一开始该cj数组中每个成绩均为0。
#pragma argsused
int main(int argc, char* argv[])
{
return 0;
}
//---------------------------------------------------------------------------
第二步:加入让用户选择功能的函数。
我们大致按从上而下方法来写这个程序。该程序首先要做的是让用户选择待执行功能。下面的函数实现这个界面。
//函数:提供一个界面,让用户选择功能:
//返回值:1~5, 待执行的功能,0:退出程序
int SelectFunc()
{
int selected;
do
{
cout << "请选择:(0~5)" << endl;
cout << "1、录入成绩" << endl
<< "2、清空成绩" << endl
<< "3、输出成绩" << endl
<< "4、查询成绩" << endl
<< "5、统计成绩" << endl
<< "0、退出" << endl;
cin >> selected;
}
while(selected < 0 || selected > 5); //如果用户输入0~5范围之外的数字,则重复输入。
return selected;
}
函数首先输入1到5项功能,及0:用于退出。注意我们用了一个do...while循环,循环继续的条件是用户输入有误。do...while流程用于这类目的,我们已经不是第一次了。
函数最后返回 selected 的值。
这个函数代码最好放在下面位置:
......
int cj[CLASS_COUNT][CLASS_STUDENT_COUNT];
/*
<<<< 前面SelectFunc()函数的实现代码加在此处。
*/
#pragma argsused
int main(int argc, char* argv[])
......
为了验证一下我们关于该函数是否能正常工作,我们可以先把它在main()函数内用一下:
int main(int argc, char* argv[])
{
SelectFunc();
}
按Ctrl + F9 编译,如果有误,只现在就参照本课程或源代码改正。如果一切顺利,则你会发现这个函数工作得很好。那么,删掉用于调试的:
SelectFunc();
这一行,我们开始下一个函数。
第三步:辅助函数:用户输入班级编号等。
“录入、清空、统计”成绩之间,都需要用户输入班级编号。而“查询成绩”则要求用户输入班级编号及学号,所以这一步我们来实现这两个函数。
//用户输入班级编号:
int SelectClass()
{
int classNumber; //班级编号
do
{
cout << "请输入班级编号:(1~4)";
cin >> classNumber;
}
while(classNumber < 1 || classNumber > CLASS_COUNT);
return classNumber - 1;
}
SelectClass 和 SelectFunc 中的流程完全一样。为了适应普通用户的习惯,我们让他们输入1~4,而不是0~3,所以最后需要将classNumber减1后再返回。
另外一个函数是用户在选择“成绩查询”时,我们需要他输入班级编号和学生学号。前面的SelectClass()已经实现班级选级,我们只需再写一个选级学号的函数SelectStudent即可。并且SelectStudent和SelectClass除了提示信息不一样,没什么不同的。我们不写在此。
第四步:录入、清空、查询、统计成绩功能的一一实现。
//录入成绩
//参数 classNumber: 班级编号
void InputScore(int classNumber)
{
/*
一个班级最多40个学员,但也可以少于40个,所以我们规定,当用户输入-1时,表示已经输入完毕。
*/
//判断classNumber是否在合法的范围内:
if(classNumber < 0 || classNumber >= CLASS_COUNT)
return;
//提示字串:
cout << "请输入" << classNumber + 1 << "班的学生成绩。" << endl;
cout << "输入-1表示结束。" << endl;
for(int i=0; i < CLASS_STUDENT_COUNT; i++)
{
cout << "请输入" << i+1 << "号学员成绩:";
cin >> cj[classNumber][i]; //cj 是全局变量,所以这里可以直接用。
//判断是否为-1,若是,跳出循环:
if( -1 == cj[classNumber][i])
break;
}
}
//----------------------------------------------------
//清空成绩:
void ClearScore(int classNumber)
{
//判断classNumber是否在合法的范围内:
if(classNumber < 0 || classNumber >= CLASS_COUNT)
return;
for(int i=0; i < CLASS_STUDENT_COUNT; i++)
{
cj[classNumber][i] = 0;
}
cout << classNumber + 1 << "班学生成绩清空完毕" << endl;
}
//----------------------------------------------------
//输出成绩:
void OutputScore(int classNumber)
{
//判断classNumber是否在合法的范围内:
if(classNumber < 0 || classNumber >= CLASS_COUNT)
return;
cout << "============================" << endl;
cout << classNumber + 1 << "班成绩" << endl;
/*
有两点要注意:
1、要求每行输出5个成绩。
2、每个班级并不一定是40个成绩,所以只要遇到-1,则停止输出。当然,如果该班
成绩尚未录入,则输出的是40个0。
*/
for(int i = 0; i < CLASS_STUDENT_COUNT; i++)
{
if(i % 5 == 0) //因为每行输出5个,所以i被5 整除,