首页 服务器 编程 必备知识 搜索引擎 圩日手册
站内搜索
最近浏览
推荐文章
热文排行

第十六章 数组(一)


16.1 引子 -- “小王”成绩管理系统

  16.1.1 “小王成绩管理系统Ver1.0”

  16.1.2 “倒退的2.0”

16.2 数组的定义及使用

  16.2.1 实例演示

  16.2.2 个数定义必须是常量

16.3 如何给数组中的元素赋值

  16.3.1 在定义数组时初始化。

  16.3.2 在定义之后为元素赋值

16.4 控制台下如何输入和输出数组

  16.4.1 输入

  16.4.2 输出

  16.4.3 数组输入输出练习

16.5 数组的尺寸

16.6 字符数组

  16.6.1 字符数组通常用于显示

  16.6.2 字符数组初始化

  16.6.3 字符数组的输入

  16.6.4 字符数组的输出

16.7 数组应用实例

  16.7.1 “小王成绩管理系统新版”

  16.7.2 “!dnalroB evol I”

  16.7.3 “数组中的玄机”

  16.7.4 “猜奖Ver 1.0”

  16.7.5 求最值

16.8 小结:我们又迈出了重要的一步
 

16.1 引子 -- “小王”成绩管理系统

 

小王老师参加了“编程摇篮”的学习,这一天他的系主任请他写程序。

系主任提的第一个要求是:

用户输入6个班级的各自的学生英语成绩总分,要求程序输出成绩总分和平均分。

 

“这简单了!”小王心想,“在前面的课程里,早就有过成绩统计的例子了嘛!改改就行。”

 

16.1.1 “小王成绩管理系统Ver1.0”

 

功能:求六个班级的成绩总分及平均分 (为了方便起见,下面的所有成绩数据都使用整型,不考虑小数)

......

int cj,zcj=0,pjcj=0;  //各班成绩,总成绩,平均成绩

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

{

  cout << "请输入第" << i+1 << "班的分数:";

  cin  >> cj;

  zcj += cj;  //累加总成绩

}

 

//求平均:

pjcj = zcj / 6;

 

cout << "总成绩:" << zcj << endl;

cout << "平均成绩:" << pjcj << endl;

......

 

小王迅速测试了一遍,没有问题,任务胜利完成!小王的形像立刻在系主任的眼里高大起来……不过,客户的需求总是在不断进步的!系主任立即提出第二个要求:

必须加入查询功能,比如说,用户输入1,则程序输出一班的成绩,输入2,则输出二班的成绩,以此类推。

 

“这可不好办了!南郁老师没有教我这个啊……”小王心里很着急,“要让用户可以查询,那至少我得让程序把这6个成绩记下。”小王来回看了好几遍for循环,也没有想出如何在for循环里记下用户输入的成绩。他决定放弃了……等等!不就6个班级吗?我不用循环 总行吧,在讲循环流程的那一章里,不是举了一个最笨的办法吗?(见10.4

(尽管这样写程序,按南郁老师说只能得到“鸭蛋”,可以桌子那边传来系主任殷切的目光……)

一阵“噼噼叭叭”,小王删除了前面所有代码,可以实现查询的新代码如下:

 

16.1.2 “倒退的2.0”

 

功能:求六个班级的成绩总分及平均分,并可实现对各班成绩查询

 

......

//定义六个变量,用于存储六个班级的成绩:

int cj1,cj2,cj3,cj4,classSocre5,cj6;

 

//老师说下面的方法很“笨”,不过没办法了,只能这样...

//(反复用 Ctrl+C,Ctrl+V 还是很方便的)

cout << "请输入第1班的成绩:" << endl;

cin >> cj1;

 

cout << "请输入第2班的成绩:" << endl;

cin >> cj2;

 

cout << "请输入第3班的成绩:" << endl;

cin >> cj3;

 

cout << "请输入第4班的成绩:" << endl;

cin >> cj4;

 

cout << "请输入第5班的成绩:" << endl;

cin >> cj5;

 

cout << "请输入第6班的成绩:" << endl;

cin >> cj6;

 

//求总成绩和平均成绩:

int  zcj = (cj1+cj2+cj3+cj4+cj5+cj6);

int  pjcj = zcj / 6;

 

cout << "总成绩:" << zcj << endl;

cout << "平均成绩:" << pjcj << endl;

 

//下面是新功能:查询:

 

char c;

 

do

{

  cout << "请输入要查询的班级次序(1~6,0:退出)" <<endl;

  cin >> c;

 

  //用switch实现对用户输入的判断及分流:

  switch(c)

  {

      case '1' : cout << cj1 << endl; break;

      case '2' : cout << cj2 << endl; break;

      case '3' : cout << cj3 << endl; break;

      case '4' : cout << cj4 << endl; break;

      case '5' : cout << cj5 << endl; break;

      case '6' : cout << cj6 << endl; break;

      //其它的,输出出错消息:

      default :  cout << "您的输入有误!" << endl;     

  }

}

while(c != '0') ; //用户输入‘0’,表示退出

......

 

手有点酸,不过毕竟实现了查询功能。小王把写好的程序拿到系主任那里一运行,主任的眼睛透过厚厚的眼镜片闪闪发光……并且开始打听“没有弯路,编程摇篮”的网址。然后他说:“小王,你这个程序太好了!我们完全有必要把它发扬光大!你这就把程序稍作修改,扩展到整个学校的5000个学生的成绩都可以查询……”

“5000个学生?我岂不要定义5000个成绩变量……”小王眼前一黑,倒在地上。

 

16.2 数组的定义及使用

请从下面的实例中讲解,数组变量与我们以前学过的普通变量在定义和使用上有什么区别。

16.2.1 实例演示

处理5000个学员的成绩,是否需要定义5000个变量?

现实生活中有大量的数据类似于此,它们有相同的类型,需要的处理的方法也一致。 为了实现对这些数据统一表达和处理,C,C++提供了“数组”这一数据结构。

 

数据类型 数组变量名[个数常量];

 

和普通变量定义的语法:数据类型 变量名;相比,主要是多了一个“[个数]”。

 

定义数组时,方括号内的数值指明该数组包含几个元素;使用数组时,方括号内的数值表示:使用的是第几个元素。为了直观,我假设可以用汉字来命名变量:

int 抽屉[100];

这里定义了一个“抽屉数组”,这个数组包含了100个抽屉。

而在使用时:

int a = 抽屉[10];

这里的10表示我们要使用第10个抽屉(其实是第11个,见后)。

 

还是来看小王老师的学生成绩管理系统吧。因为主任说要管理5000个学生成绩,那么,我们就得定义一个成绩数组,包含5000个元素。

 

int  cj[5000];

 

这一行我们就相当于定义了5000个int类型的变量。这5000个变量有个统一的名字:cj。为了区分出每个变量,还需要使用“[下标]”的格式指明。下标从0开始,一直到4999。比如,第一变量是: cj[0],第二个为:cj[1]……最后一个是cj[4999]。

强调:在C,C++中,数组的下标从0开始。因此,我们定义了数组 int cj[5000];得到的是从cj[0] ~ cj[4999]的变量,而不是cj[1] ~ cj[5000]。

 

比如,让第100个学员的成绩为80:

cj[99] = 80;

为什么是99而不是100?因为数组的下标从0开始。

再比如,让变量a等于第100个学员的成绩:

int a = cj[99];

 

有了“数组”这个新武器,要解决前面全校成绩查询问题,一下子变得简单了。

 

......

//定义一个含5000个元素的数组,用于存入5000个学生的成绩:

int cj[5000];

 

//总成绩,平均成绩:

int zcj=0, pjcj;

 

//仍然可以用我们熟悉的循环来实现输入:

for(int i=0; i<5000; i++)  //i从0开始,到第49999(含)个结束。

{

   cout << "请输入第" << i+1 << "学员的成绩:";

   cin >> cj[i];      //输入数组中第i个元素

  

   //不断累加总成绩:

   zcj += cj[i];           

}

 

//平均成绩:

pjcj = zcj / 5000;

 

//输出:

cout << "总成绩:" << zcj << endl;

cout << "平均成绩:" << pjcj << endl;

 

//下面实现查询:

int i;

 

do

{

   cout << "请输入您要查询的学生次序号(1 ~ " << 5000 << "):" ;

   cin >> i;

 

   if( i >= 1 && i <= 5000)

   {

      cout << cj[i-1] << endl; //问:为什么索引是 i-1,而不是 i ?

   }

   else if( i != 0)

   {

      cout << "您的输入有误!" << endl;

   }

}

while(i != 0);  //用户输入数字0,表示结束。

......

 

代码用了两个循环——是的,有了数组,你会发现循环流程将用得越来越多——第一个 for 循环用以输入5000个成绩。第二个do...while 循环用以查询。用户可以输入1到5000之内的数字(和现实生活习惯一致)。

 

下面要复习“宏”的知识。如果学校多了一个学生,那么我们就得将上面所有以斜体标出的“5000”改为5001。这是一件让人觉得无趣,且很容易出错的工作。另外,现在我们要调试这段程序,难道你真的有耐心输入5000个成绩?在调试时,我们可能希望只输入5个成绩。

这时我们可以使用“宏”。下面的代码中,先是定义了一个宏:MAX_CJ_COUNT,而后所有立即数5000都用它来表示。当我们需要改变人数时,只需更改MAX_CJ_COUNT的宏定义。

 

//定义一个含5000个元素的数组,用于存入5000个学生的成绩:

#define MAX_CJ_COUNT  5000 //定义一个宏,方便修改为实际学生人数。

int cj[MAX_CJ_COUNT];

 

以下代码略。有关宏的用处,请复习:5.3.2 用宏表示常数

 

除非确实可以一次决定某数组的大小,否则,使用一个意义明确的宏来定义数组的大小,总是一个不错的主意。

16.2.2 个数定义必须是常量

再一看眼数组定义的语法:

数据类型 数组变量名[个数常量];

 

注意“常量”两字,这说明,个数必须是一个可以事先决定的值,并且该值不能被改变。

比如用立即数:

int arr[5000];

 

或者用宏:

#define MAX_CJ_COUNT  5000

int arr[MAX_CJ_COUNT];

 

或者用常量:

const int max_cj_count = 5000;

int arr[max_cj_count];

 

就是不能用变量:

int max_cj_count =  5000;

int arr[max_cj_count];   // error! 不能用变量指定数组的大小。

 

为什么呢?因为数组占用的内存空间大小必须在程序编译时决定,并且一旦决定了,就不能再改变。所以只能用常量(不变的量)来指明数组的大小。当然,这是指在数据区或栈区分配内存(和程序有一样的全存期),如果是在堆区,则可以动态地分配数组的大小,这些我们在指针一章里讲。

有关常量和变量的区别,如果有所不清,请前面章节内容。

 

16.3 如何给数组中的元素赋值

如果把单个变量看成是“游兵散勇”的话,那么数组对应的是“集团”。集团的“兵”就是我们前面说的数组的元素。这些“兵”不再有单独的名字,而是统一使用编号来区别,这个编号,我们称为“下标”。

在和数组打交道时,我们需要分清:是对整个数组操作,还是对数组中的单个元素进行操作。

 

16.3.1 在定义数组时初始化。

普通变量可以在定义时同时赋初值:

int a = 100;

也可以在定义以后才赋值:

int a;

a = 100;

 

对于数组变量,则只能在定义时,对整个数组赋初值:

 

数据类型 数组变量名[个数] = {元素1初值,元素2初值,};

 

即,将初值用一对 {} ( 花括号 )括起来,相邻的值之间用逗号分隔。

 

比如:

int arr[10] = {9,8,7,6,5,4,3,2,1,0};

上面定义一了个数组 arr,共10个元素。初始值为从9到0。即,执行上面代码以后,arr[0]值为10,arr[1]值为9……arr[9]值为0。

 

在初始化赋值是时,注意所给值的个数不能超过数组的大小,比如:

int arr[10] = {10,9,8,7,6,5,4,3,2,1,0};  //错误!越界了

你定义了数组为10个元素,可是你却赋给它11个值,这称为数组越界:你在宾馆里预定了10间房,你却让11个人去住,多出的那个人挤在哪里呢?编译器遇上这类问题时,很明智地停了下来。

 

不过,你可以给它少于定义个数的初值

int arr[10] = {9,8,7,5};               //允许

你定义了数组为10个元素,但你可以给它少于10个的初始值。那些没有得到初始值的元素值是多少呢?对于全局变量来说,将被编译器初始化为0,对于局部变量而言,则是未定义的值(不可预测的值)。如果只定义数组,没有对其进行任何初始化,同样适于本情况。所有元素的初值均依本数组是全局或局部变量而定,为0或未定义值。

 

可以跳过一两个元素不初始化吗?如:

int arr[10] =  {9,,7,,6};  //跳过中间的某些元素,C:OK;C++: Error

因为我们主要学习C++,所以认为跳过数组中某些元素的初始化赋值是错误的

也就是说,你尽可以先预定下多个房间,然后先只派部分人去住。不过C说:允许你们跳着房号住,而C++则要求依次住下,把空房留在后面。

 

你还可以不指定数组元素个数,直接通过对其初始化来让编译器得到它的实际个数:

 

int arr[] = {9,8,7};  //元素个数为: 3

即,在未指定义大小时,则初始值的个数就是数组元素的个数

 

不过,你不能既不指定数组元素个数,也不进行初始化

int arr[];   //Error, 到底是几个元素?

这也没有什么不好理解的,去几个人,就开几个房间。让宾馆老板自已去数人头,我们不必非得自已报人数——前提是,房客一个一个的都得事先现身。

 

16.3.2 在定义之后为元素赋值

很多时候,我们无法在定义数组的同时就知道数组中各元素的值,所以,这时需要在定义以后各数组中的各个元素赋值。记住,此时只能对单个元素进行直接操作。这和普通变量不一样,下面的代码是错误的:

 

int arr[5];

...

arr[5] = {1,2,3,4,5}; //错,在编译器看来,arr[5]是数组arr的第6个元素。

//或

arr = {1,2,3,4,5};   //错,仍然不行。

这一点和普通变量不一样。也就是说,对数组整体的初始化,只能在定义时实行。

 

大都数情况,我们这样改变数组中某个元素的值:

 

int arr[5];

 

arr[0] = 95; 

arr[1] = 89;

arr[2] = 100;

...

 

前面关于成绩管理的例子中,已经有过如何改变数组元素值的代码:cin >> cj[i]; 这一句将用户输入的成绩赋给数组cj中的第i个元素(i从0计起)。

 

两个数组可以相互赋值吗?答案也是不行:

 

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

int arr2[5];

 

arr2 = arr2; //不行!整个数组之间不能直接相互赋值。

 

你可能很不理解为什么上面的代码会出错,不过在学习后面有关指针及内存地址的更多知识以后,你会明白。现在我们只能告诉你,以我们的所学,可以方便地用一个循环来 实现将一个数组内的值全部赋值给另一个数组,这也称为数组间的拷贝。

 

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

{

    arr2[i] = arr1[i]; //正确,数组元素之间可以相互赋值。

}

 

当然,这样做可一定要注意:当两个数组的元素个数不一致时,不要越界访问:

 

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

int arr2[6];

 

for(int i = 0;i<6;i++)  // i 从 0 到 5

{

    arr2[i] = arr1[i];

}

 

arr2有6个元素,而arr1只有5个。当循环执行到 i为5时,访问arr1[5]将造成越界。

(越界可以直观地理解为“越出边界“,即越出数组的边界,访问到了数组以外的内存,其内容将是未知的。)

 

16.4 控制台下如何输入和输出数组

 

输入和输出属于程序设计中,用户界面这一方面的内容。在Windows图形界面和控制台或DOS的字符界面下会完全不同。

 

16.4.1 输入

先说输入:

以前要让用户输入个某个变量的值,很简单,比如让用户输入一个学成成绩(整型):

 

int cj;

cin >> cj;

 

现在,cj是一个数组: int cj[5];

那么要输入这5个成绩,是否可以直接写:cin >> cj;

你可能猜到了:不行。不信可以试试(建议:不管你信不信都请试试)。

为什么不行?原因是当 cin 碰上 cj时,它只知道 cj 是整型数组,但它不知道这个数组多大。你可能又要问,为什么cin能知道cj是一个数组,却不能知道这个数组有多大呢?这又涉及到变量与内存关系。我们先来看这个图:

(图: cin 遭遇数组,cin只“看”到数组中的第一个元素)

 

数组中的各个元素总是连续地存入在内存里。所以在C,C++里,为了效率,数组变量的传递,总是只传递第一个元素的内存地址,(后面的元素自然紧跟着)。所以,当cin得到 cj这个数组时,它只看到第一个元素,它知道第一个元素的大小,却并不知道整个数组的大小,也不知道到底有多少个元素在这个数组里。这就有比你是一个窗口的售票员,你知道外面来买票的是一个队伍,但你不知这个队伍到底有多少。当然,C、C++也提供“卖团体票“的函数,但那就需要向这个函数额外传递一个数组的大小的参数。

 

说了这么长关于输入的问题,其实解决方法我们早于用过,来一个循环流程,一个一个输入即可:

 

int arr[5];

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

{

   cin >> arr[i];

}

 

16.4.2 输出

再说输出。

 

可想而已,想一次性输出一个数组中的各元素的值,也是不可能的:

 

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

 

cout << arr; //并不能输出arr内5个元素的内容。

 

你不能就此放过这个问题,我一直建议要多动手,多试试,这次就可以看出作用。当你试着在C++ Builder内写上面的代码,然后编译,你会发现编译器并没有报错。程序也可以运行。

请照着下面的代码“试试”:

 

#include <iostream.h>

int main(int argc, char* argv[])

{

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

   

    cout << arr; //提问:输出一个数组,结果会是什么?

 

    cin.get();  //从本章起,不再使用getchar();改为本行

}

 

这运行结果截图:

 

输出了一个怪怪的数值。这个数值正是数组 arr 在内存中的“门牌号”——地址。并且,数组的地址其实也正是数组中第一个元素的地址。关于数组与内存地址的关系。我们在下一章详细讲解。这里要关心的正事是如何输出数组中的每个元素的值,代码如下:

 

int arr[5];

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

{

   cout << arr[i];

}

 

同输入一样,还是对元素操作。

 

16.4.3 数组输入输出练习

现在请大家打开CB,新建一控制台工程。实现以下要求。

 

接受用户的键盘输入10个整数,然后输出这10个数,每行输出1个数。

提示:

1、别忘了文件开始位置加入本行:#include <iostream.h>

2、由于Windows操作系统的问题,使得其控制台环境下,getch()cin.get()在前面有用户输入数值并且回车的情况下,将直接接受回车字符,导致起不到“暂停”的作用。解决方法为:使用两行:

cin.get();

cin.get();

:

getchar();

getchar();

从本章开始,我们将改为使用cin.get()来起暂停作用。请大家注意。

 

16.5 数组的尺寸

这里的尺寸说的是数组占用内存空间的大小,即某个数组占用了多少个字节。

以前我们说一个int型数据占用4个字节,那么一个有n个元素的整型数组占用了多少个字节?几乎可直接猜到:4 * n 个字节。而一个有n个元素的字符型或bool型数组,则应占用 1 * n 个字节,因为它们单个元素的大小均为1字节。

我们用sizeof测量某个变量占用的字节数:

int a;

int size = sizeof(a);

size 将得到 4。

 

同样,我们可以这样来得到数组的尺寸:

int arr[10];

int size = sizeof(arr);

size 将得到 40

你可能会问,为什么前面的 cin 不知道 arr的尺寸,而sizeof却可以得知?因为sizeof事实上是在编译阶段就进行计算。

 

得知数组的尺寸的一个最大的用处,就是可以在程序里通过计算得到这个数组包含几个元素。这相当于一道小学算术题:

已知某个数组占用40个字节的空间,并且知道该数组单个元素占用4个字节的空间,请问这个数组包含多少个元素?

看:

int arr[] = {1,2,3,4,5,6,7,8,9,0};

//计算arr数组内有多少个元素:

int count = sizeof(arr) / sizeof(arr[0]);

 

count 将得到10,即arr数组中元素的个数。

其中 sizeof(arr) 得到 整个数组占用的字节数,sizeof(arr[0]) 得到单个元素占用的字节数。数组的基本概念就是每一个元素的大小是并且必须一致的(你可以推想以后我们会学到内中元素可以大小不一的数据结构),为什么我们要取第1个(下标为0)元素? 看看下例:

 

int arr[] = {0};

int count = sizeof(arr) / sizeof(arr[0]);

明白了吗?我们或许不知道或arr有几个元素,但有一点,并且只有一点可以确定:只要是数组,那么它就至少会有一个元素。可以有 int arr[1];但不允许有:int arr[0]

 

最后,对于简单的数据类型,sizeof还可以对其类型进行求值:

int a;

int size = sizeof(a); size 将得到4

 

而直接对 int 操作:

int size = sizeof(int); size也将得到4.

 

数组没有和后者对应的做法。虽然数组也可以有不同类型:整型数组,实型(浮点型)数组,布尔型数组,字符型数组,但数组的大小不仅和它的类型有关系,而且和它含有几个元素有关系。以公式表达:

数组的大小 =  数组类型的大小 * 元素个数。

或者:

sizeof(数组) = sizeof(数组类型) * 元素个数

或者:

sizeof(数组)= sizeof(数组[0]) * 元素个数

 

16.6 字符数组

字符数组具有一些特殊性,主要在三方面:1、初始化,2、输入输出、3、结束字符。大家需要注意了。

16.6.1 字符数组通常用于显示

前面我们用整型表示成绩,所以数组:int cj[5000];是一个整数数组,或整型数组。

其它的数据类型,如果浮点型:float、布尔型:bool 或者字符型 char 均可以有相关的数组,如:

int age[] {25,32,29,40};

float money[] = {1000.50,2000,2870.95};

bool  married[] = {false,true};

char name[] = {'M','i','k','e'};

 

最后一行定义了一个字符数组,用来存储一个英文人名:“Mike”。相对于其它数据类型的数组而言,我们将更有需要将字符数组输出。比如上面的name,如何将“Mike”输出到电脑屏幕呢?依据上一小节的知识,应该是:

 

char name[] = {'M','i','k','e'};

for(int i=0; i<sizeof(name) / sizeof(char); i++)

   cout << name[i];

 

上面代码的屏幕输出是: Mike

 

假设有一个整型数组:

int age[] {25,32,29,40};

当我们要输出它时,大抵应是希望输出如:

25 32 29 40  或者加上逗号: 25,32,29,40

或者干脆换行:

25

32

29

40

 

但对于字符串数组,我们更多地希望它如上面“Mike”一样连续输出。C,C++ 设计者们看到了这一点,所以针对字符数组,有以下特殊做法。说是特殊,是指其它类型的数组没有,对于人的习惯来说,它倒是非常“自然”的做法。

 

16.6.2 字符数组初始化

首先是初始化部分,字符数组允许这样实现:

 

char name[] = "Mike";

 

对于中国人来说,这是一个“救命”的做法,我们不用去“拆”汉字了——还记得吗?一个汉字占两个字节,即一个汉字其实是由两个字符组成的。

 

char myname[] = "南郁";

 

当然,明确指定元素个数也不违法:

 

char name[5] = "Mike";

 

为什么我指定 name 的元素个数为5? 这是一件更重要的问题。后面会说到。

 

另外,如果你习惯于给数组加花括号,也可以:

 

char name[] = {"Mike"};

当以后讲到二维数组,花括号就是必须的了。

 

16.6.3 字符数组的输入

然后是输入:

 

char name[4];

cout << "请输入您的姓名,不要超过4个英文字符或2个汉字!" << endl;

cin >> name;

 

这是运行的结果:

 

每一个(正直 && 认真学习)的学员都会指出老师的自相矛盾:前面刚刚说,当“cin 遭遇数组,cin只‘看’到数组中的第一个元素”,即:cin 不知道 name 的大小!

事实上 cin 仍然不知道 name 的大小。所以上段代码一开始就“请求”用户只输入4个以内的字符。用户当然不会那么听话!如果他输入5个,或更多字符,会发生什么糟糕的事情?如果你试试,你可能会发现一切正常,但其实性质严重的错误确实存在。就像一个小孩偷了家里的东西,他倒不会因此被派出所抓走(因为大多数家庭会选择内部处理),但错误的严重性值每个父母重视。

 

有没有办法让 cin 指接受指定个数的字符?而不管具有“破坏欲望”的用户输入多长?

 

(本来, cin, cout 这一类仅在 DOS或控制台下或UNIX或Linux下的字符界面下才能用的东东,都不是我们的学习目标。记住,我们只是为了学习C,C++基础知识上的方便,才染指。在讲如何实现前述要求之前,我们来插一个“广告”吧 :)放松放松。

想像南郁老师是电视里的那个漂亮的幼儿园大班女阿姨……

大家——想像时光倒流,回到无忧无虑的童年时代,脸上露出天真无邪的笑容——为了配合场景,还得想像人手一张花花绿绿的盗版Windows XP 安装盘,用左手拿,举着,摆好POS。

好了,本阿姨问:“我们的目标是~~~”。

大家齐声回答:“Windows 编程!!”

 

呵呵,回答“没有弯路”也许更像一点。 Windows编程的课程即将出笼。。。一开始将都是免费的,暂不收费。但是若要为了“没有弯路“,《白话 C++ 》一定要学完学好。所以你若没有C++基础,建议不要放弃而跑到去学习《白话 Windows编程》。)

 

(下面这一段是选学内容)

cin.getline的用法:

 

getline 是一个函数,它可以接受用户的输入的字符,直到已达指定个数,或者用户输入了特定的字符。

它的函数声明形式(函数原型)如下:

 

istream& getline(char line[], int size, char endchar = '\n');

 

不用管它的返回类型,来关心它的三个参数:

char line[] 就是一个字符数组,用户输入的内容将存入在该数组内。

int size : 最多接受几个字符?用户超过size的输入都将不被接受。

char endchar :当用户输入endchar指定的字符时,自动结束。默认是回车符。

 

结合后两个参数,getline可以方便地实现: 用户最多输入指定个数的字符,如果超过,则仅指定个数的前面字符有效,如果没有超过,则用户可以通过回车来结束输入。

 

char name[4];

cin.getline(name,4,'\n');

由于 endchar 默认已经是 '\n',所以后面那行也可以写成:

cin.getline(name,4);

 

如果你想输入三个汉字,把 name 改成6即可。作为配合,你可以用一个宏来指定name的元素个数,也可以用前面的sizeof的方法:

 

char name[6];

cin.getine(name,sizeof(name));

 

16.6.4 字符数组的输出

和输入对应,字符数组也允许不用循环,直接用 cout 输出。

 

char name[] = "Mike";

cout << name;

 

上面代码的屏幕输出是: Mike 。效果和前面使用 for循环的代码完全一样。

 

同样的问题是,cout 怎么会知道 name 内有四个字符呢?

为了加深你对这个问题的理解,先来看下面的程序:

 

char name[] = {'M','i','k','e'};  //我们用“老”的方法初始

cout << name;

 

运行结果是: 在“Mike”后面,接了一个怪字符。为什么?因为 cout 不知道 name 中有几个字符,结果出输出过头了。你的机器上可能“Mike”之后不是这个怪字符,它是随机不定的。

 

或许这样的结果还是不足于让你“感动”。下面这个例子绝对值得你去一试。我写完整一点:

 

#include <iostream.h>

 

char name[] = {'M','i','k','e'};

char othername[] = "Tom";

 

int main(int argc, char* argv[])

{

  cout << name << endl;

  cin.get();

  

  return 0;

}
 

运行结果是:

 

在内存里,由于数组:othername 紧挨着 name 后面,而cout 在输出name时,它并不知道 name中有几个元素,所以把othername 的内容也当成是name的,一并输出了。这应该是一个明明白白的错误表现了。

注意,如果你想把nameothername的定义放在 main 函数内,那为了达到上面的效果,你应把二者的次序倒过来:

 

int main(int argc, char* argv[])

{

  char othername[] = "Tom";       //改成它先定义。

  char name[] = {'M','i','k','e'};

 

  cout << name << endl;

  cin.get();

  

  return 0;

}
 

这是因为函数内的局部变量是建立在栈内存,而栈内存是“倒立”的。

 

如何让 cout 知道 它要输出的字符数组有几个字符?不是依靠 cout 提供对应于 getline()的函数,而是由 C++ 内建机制,提供一个间接方法来保证程序有办法断定一个字符数组的结束。这就是:为字符数组添加一个空字符为结束。

先来看答案:

 

char name[] = "Mike";

cout << name;

 

就是这么简单,奥妙在于初始化 name 时,请采用这种“特殊”而又“自然”的方法:直接给出用双引号括起来的字符串。当你用这种方法初始时,编译器在编译过程中,将偷偷地为在这个字符数组后面加上一个元素。这个元素就是空字符。

(什么叫空字符?也称“零字符”就是值为0的字符,表示为:'\0' 如果忘了,请复习第五章。)

 

只有使用 "Mike" 这种方法,编译器才会做这件附加的工作。采用 {'M','i','k','e'}这种方法时,空字符不会被添加。

 

添加了空字符,cout 就能知道 name 有多长吗?其实它仍然不知,但它不管,它只需一个一个字符地输出,直到碰上空字符,即:'\0'时,它就认为这个字符串结束了。

 

char name[] = "Mike"; 中,编译器真的往 name 后加一个空字符了吗?

 

我们用两个办法来验证。

 

第一、通过调试工具观察

 

眼见为实。字符数组最后的空字符仅用于表示当前字符串结束了,本身并不输出(即使输出,也是一个不占位置的字符,看不到)。但是当程序运行时,它在内存是的的确确占了一个字节,我们可以通过调试来查看。

 

新建一个控制台工程,添加以下黑体部分代码:

#include <iostream.h>

...

int main(int argc, char* argv[])

{

   char name1[] = {'M','i','k','e'};

   char name2[] = "Mike";

 

   cout << name1 << endl;

   cout << name2 << endl;

 

   return 0;

}
 

在其中的某行 cout << ... 上设置断点,然后运行,程序停在断点上,如下图:

 

将输入鼠标分别在移到代码中的 name1name2 上,按Ctrl键,并用鼠标点击,将出现Debug Inspector窗口(调试查看器)。

可以清楚看到,name2 多了一个元素,并且值正是: '\0'。它就是编译器自动加入字符串结束标起:空字符。

 

 

第二、既然加了一个字符,那么,name[] 的大小就应该是5,而非4。为了加深记忆,我们用程序验证。

 

把上面的相关代码做点改动:

 

char name1[] = {'M','i','k','e'};

char name2[] = "Mike";

 

cout << sizeof(name1) << endl;

cout << sizeof(name2) << endl;

 

结果:

4

5

 

正如我们所想,第一种初始化方法,会让 name1的尺寸多出一个字节,那个字节用于存入空字符。

由于引出一个重要的注意事项:指定字符数组的大小时,请注意为空字符预留一个字节。

 

下面是一个反例:

char name[4] = "Mike";

cout << name;

由于你强制限定name为4个元素,所以编译器爱莫能助,无法为它加上空字符作为结尾。当cout仍然无法正确地输出。

 

正确的是:

char name[5] = "Mike";

最不会犯错的不写:

char name[] = "Mike";

 

反过来,当采用 {'M','i','k','e'} 的形式初始化时,我们可以手工添加一个空字符:

char name [] = {'M','i','k','e''\0'};

cout << name;

 

由于有了'\0'作为结束,所以cout可以正确地输出name

 

请回答,下面的代码运行后输出应为?

char name [] = {'M','i','k','\0','e'};

cout << name;

 

16.7 数组应用实例

 

我们将举几个例子,例子从简单到复杂。要求大家每个都照课程做一遍。我们不给出源代码。

以下例子均需要包含 #include <iostream.h>。若另需其它头文件,我们在例子中指出。

 

16.7.1 “小王成绩管理系统新版”

 

请自定义一个成绩数组(这回使用float类型),并求出其总分和平均分

 

//为了不重复太多内容,我这里直接用一个现成的数组

//你可以把它改回通过用户输入的方法得到成绩。

float cj[] = {85.5,90,100,70,60,45.5,89};

 

float pjcj,zcj = 0;

 

//成绩个数:

int cjCount = sizeof(cj) / sizeof(cj[0]);

 

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

{

   zcj += cj[i];

}

 

pjcj = zcj / cjCount;

 

cout << "总分:" << zcj << endl;

cout << "平均:" << pjcj << endl;

 

cin.get();

 

在作业中,我们要求大家继续完善本例,让它可以变得专业:

1、可以根据平均分,评判本次试卷的出题质量。(每年教委内部都要对高考试卷进行类似的评析)

2、学员输入自已的座号,可以输出他的成线和计算机对他的评价。

3、可以求最高分和最低分。

 

16.7.2 !dnalroB evol I

 

请自定义一字符数组,先将其输出,然后逆序输出(不包括空字符)

 

char str[] = "I love Borland!";

 

//正常输出:

cout << str << endl;

 

//字符串长度:

int len = sizeof(str) - 1; //问:为什么要减1?

 

//逆序输出:

for(int i=len-1; i>=0; i--) //问:为什么 i 从 len-1开始?

{

  cout << str[i];

}

 

这是输出结果:

 

请在完成本例后,继续尝试这一要求:

如果要求使用这样的for循环:

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

{

   //?????

}

仍然要实现逆序输出,请问循环体的那行代码该如何写?这也是我们本章一道作业。

 

16.7.3 “数组中的玄机”

这个有点意思

 

有一整型数组:

int flags [] = {

   0,0,0,0,1,1,3,4,6,5,3,3,5,5,6,4,3,2,2,-1,

   0,0,0,0,0,2,0,0,0,6,0,0,0,0,6,0,0,1,-1,

   0,0,0,0,7,0,0,0,1,10,0,13,0,10,2,0,0,0,8,-1,

   0,0,0,0,8,0,0,0,0,1,0,0,0,2,0,0,0,0,7,-1,

   0,0,0,0,13,4,6,9,9,13,4,14,4,13,9,9,6,4,13,-1,

   0,0,7,3,11,12,4,12,4,12,96,9,12,4,12,4,12,11,3,8,-1,

   0,0,8,13,2,2,0,0,0,0,4,12,4,0,0,0,0,1,1,13,7,-1,

   0,0,0,0,12,9,0,0,15,0,15,0,15,0,0,0,12,-1,

   0,0,0,0,4,12,9,0,0,16,17,18,19,0,9,12,4,-1,

   0,0,0,0,7,0,4,12,9,0,0,0,0,0,0,9,12,4,0,8,-1,

   0,0,0,13,8,0,0,8,4,12,9,0,9,12,4,7,0,0,7,13,-1,

   0,0,1,1,1,13,13,13,2,0,4,12,4,0,1,13,13,13,2,-1,

   };
 

请根据该数组中各元素的值依次做如下输出:

 

0 => 空格

1 => (

2 => )

3 => `   在数字键 1 的左边

4 => 单引号

5 => "  双引号

6 => -  连字符

7 => / 

8 => \

9 => .

10 => o 小写字母 o

11 => ; 分号

12 => #

13 => _ 下划线

14 => =

15 => 16 => 我、17 => 爱、18 => 你

19 => !

 

注意除了 15,16,17,18 为汉字以外,其它均为英文状态下输入字符。而当输出汉字时,记得使用 "",因为一个汉字相当于个字符,所以应算字符串。另外:单引号、双引号、反斜杠 需要使用转义字符,详见下面代码。

 

看看输出结果是什么?其实这是我们小时候的游戏啦。一张看似乱七八糟的图,旁边写着:“请小朋友把图中带点的小格子涂上红颜色,看看图中隐藏着什么?”——呵呵,童年真好。快打开CB,让我们一起回到童年。

 

这是代码:

 

int flags [] = {

   /*: 从上面拷贝数组元素的值*/

   };
 

[wangjy17908]
添加时间:2008-04-12
版权所有(C)2005-2015