第二十一章 指针 三 实例演练与提高

 

21.1 简单变量、数组、指针

21.2 小王成绩管理系统V2.0 的问题

  21.2.1 软件升级历史

21.3 指针的最常用用法

  21.3.1 分配内存

  21.3.2 访问指针指向的内存

21.4 小王成绩管理系统 V3.0

21.5 字符串指针

  21.5.1 为字符串分配指定大小的空间

  21.5.2 字符串常用函数

    21.5.2.1 字符串比较

    21.5.2.2 字符串复制

21.6 指针数组

  21.6.1 什么叫“指针数组”?

  21.6.2 指针数组实例一

  21.6.3 指针数组实例二

  21.6.4 字符串指针数组 

 

21.1 简单变量、数组、指针

学习的知识点越来越多了……

刚开始会觉得很兴奋啊,学得越多越好嘛。可慢慢的就会感到压力了,各种知识点在头脑里混在一起,每个都变得模糊了。

其实,每个知识点都有它存在,或出现的理由,只要我们多做对比,就会发现学习的知识点越多,反倒越容易理解每个知识点本质。

比如说,简单变量、数组、指针,三者都是C++中用于表达数据的工具,但在表达能力上,又各有不同。

如果用建筑上的房间来比喻:

 

简单变量是一间房屋。优点是占用空间少,建筑时间短,缺点是一间房子只适于住一个人;

数组是房间数固定的一排房子,每个房子里头同样只住一人,但由于它有多间,所以适于多人居住,优点是可以统一管理多人,缺点一来是占用空间大,二来房间数一旦确定,就不能改变了。先头盖了10间,如果如果来了11个人,就有一人住不下,如果来了9个人,就有一间浪费。

指针呢……它不是实际房子,而是设计纸上的房子。因此,它首先有一个特点:如果你想让指针存储数据,那一定得先为它分配内存。这就像光有一张设计蓝图是解决不了四代同堂的问题的,重要的是你还得根据这张蓝图,去找块地皮盖好房子。指针的优点是可以临时决定要盖多少间房子。

下面我们回顾一个例子,以理解三者的不同用处。

 

21.2 小王成绩管理系统V2.0的问题

先回顾一下该程序的升级过程,今天我们将对它做出两种不同方向的改进。

21.2.1 软件升级历史

V1.0 : 本版成绩管理系统实现让计算机自动统计6个班级的成绩总分和平均分。

V2.0 : 经过改进,本版可以实现多达5000个学生的成绩进行求总分和平均分,并且可以支持用户输入序号,查询任意一个学生的成绩!

 

在第一版,小王正在学习“循环流程”。通过在每次循环中,让用户输入一个成绩,然后保存在一个简单变量里,并累加到另一个简单变量,最终计算出总分和平均分。

第二版,由于段长要求不仅可以统计5000个学员的成绩,而且应实现成绩查询功能,这就要求程序必须同时记下5000个学生成绩。小王先是想用5000个简单变量来记下成绩——这显然太不实际了,后来学到数组,用数组轻松解决了这个问题,因为数组正是为“同时存储多个相同类型的变量”这一问题来设计的。

然而,第二版存在的不足也是显然易见的。那就是,它固定只能处理最多5000个学员的成绩。假想,这个软件要推广到全市300个学校,每个学校的学生总数都是不一样的,更惨的是每一年,一个学校的学生个数总是会有变化。难道就让我们的王老师时不时地改它的程序?

在没有指针时,惟一办法就是,浪费一点,比如定义数组元素个数为1万。目的是宁可浪费一点,也尽量不要出现不够的情况。显然,本办法只能算是一个无奈之举。难道就没有一个办法,即可以适应某个山区小学只有30名学员也情况,又可以轻松对付某大学高达2万名学员的情况?

 

锣声响起,锵锵锵……指针出场了。

 

指针是如何完成这一历史使命?带着问题,我们来学习下面的内容。

我们会在学习新内容之中,同时有选择地做一些旧知识点的复习工作。但如果你仍看不懂下面的一些代码,那得全面复习前两章的指针内容;或者,如果你连for都有些陌生,那你得重温一下小王成绩管理系统的前两个版本。

 

21.3 指针的最常用用法

21.3.1 分配内存

如何为指针分配和释放内存,上一章的内容中讲到了C++独用的new/deletenew[] / delete[] C 使用的malloc, realloc/ free 方法。如果你忘了,请先复习。我们这里使用C++的方法演练。

 

new 只能为我们分配一个简单变量的内存,就是说new只盖了一间房子。new [] 才能为我们盖出一排的房子。

 

例子:

int* p; //定义一个整型指针

 

p = new [10];  //new [] 为我们分配出10个int大小的内存。(盖了10间房,每间住一个整数)

 

21.3.2 访问指针指向的内存

前面:p = new [10]; 为我们分配了10个int,那么,我们该如何设置和访问这10个整数的值呢?

这一点完全和数组一致,我们来看数组是如何操作:

 

int a[10]; //以数组方式来定义10个int

 

//让第1个整数的值为100:

a[0] = 100;

//让第2个整数的值为80

a[1] = 80;

 

指针的操作方式如下:

 

int* p = new int[10]; //定义1个整型指针,并为它分配出10个int的空间

 

//让第1个整数的值为100:

p[0] = 100;

//让第2个整数的值为80

p[1] = 80;

 

对比以上两段代码,你可以发现,对指针分配出的元素操作,完全和对数组的元素操作一致。不过,指针还有另一种对其元素的操作方法:

 

int* p = new int[10]; //定义1个整型指针,并为它分配出10个int的空间

 

//让第1个整数的值为100:

*(p+0) = 100;

//让第2个整数的值为80

*(p+1 = 80;

 

请大家自己对比,并理解。如果觉得困难,请复习第19章关于*的用法,和指针偏移部分的内容。

 

21.4 小王成绩管理系统 V3.0

 

3.0 版的最重要的改进就是:用户可以事先指定本校的学生总数。

 

请仔细看好。

 

//定义一个指针,用于存入未知个数学生的成绩:

int* pCj;

 

//总成绩,平均成绩:

int zcj=0, pjcj;

 

//首先,要求用户输入本校学生总数:

int xszs; //学生总数

cout << "请输入本校学生总数:";

cin >> xszs;

 

//万一有调皮用户输入不合法的总数,我们就不处理

if (xszs <= 0)

{

    cout << "喂,你想耍我啊?竟然输入一个是0或负数的学生总数.我不干了!" << endl;

    return -1; //退出

}

 

pCj = new int[xszs];

 

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

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

{

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

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

  

   //不断累加总成绩:

   zcj += pCj[i];           

}

 

//平均成绩:

pjcj = zcj / xszs;

 

//输出:

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

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

 

//下面实现查询:

int i;

 

do

{

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

   cin >> i;

 

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

   {

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

   }

   else if( i != 0)

   {

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

   }

}

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

 

//最后,要释放刚才分配出的内存:

delete [] pCj;

......

 

请大家现在就动手,实现小王成绩管理3.0版。这是本章的第一个重点。通过该程序,你应该可以记住什么叫“动态分配内存”。

 

21.5 字符指针

21.5.1 为字符串分配指定大小的空间

 

有必要的话,你应复习一下第16章之第6节:字符数组。

 

假设有个老外叫 "Mike",以前我们用字符数组来保存,需要指定是5个字符大小的数组:

 

char name[5] = "Mike";

 

"Mike"长4个字符,为什么要5个字符的空间来保存? 这是因为计算机还需要为字符串最后多保存一个零字符:'\0'。用来表示字符串结束了。

 

在学了指针以后,我们可以用字符串指针来表达一个人的姓名:

 

char* pname = "Mike";

 

此时,由系统自动为pname 分配5个字符的位置,并初始化为 "Mike"。 最后一个位置仍然是零字符:‘\0’。

 

采用字符串的好处,同样前面所说的,可以在程序中临时决定它的大小(长度)。

比如:

 

char* pname;

pname = new char[9]; //临时分配9个字符的大小。

 

除了要记得额外为字符串的结束符'\0'分配一个位置以外,字符串指针并没有和其它指定有太多的不同。

 

既然讲到字符串,我们就顺带讲几个常用的字符串操作函数

 

21.5.2 字符串常用函数

 

字符串操作函数的声明都包含在该头文件: <string.h>

 

21.5.2.1 字符串比较

 

int strcmp(const char *s1, const char *s2);

 

比较s1 s2 两个字符串,返回看哪个字符串比较大。对于字母,该比较区分大小写

返回值:

  < 0   s1 < s2;

    0   :  s1 == s2;

  > 0   :  s1 > s2;

 

int strcmpi(const char *s1, const char *s2);

该函数类似于上一函数,只是对于字母,它不区分大小写,比如它认为'A''a' 是相等的。

 

要说两个字符串相等不相等,还好理解,比如: "Borland" "Borlanb" 显然不相等。不过,字符串之间还有大小之分吗?

对于字母,采用ASCII值来一个个比较。谁先出现一个ASCII值比较大的字母,谁就是大者。比如:"ABCD" "AACD"大。

如果一直相等,但有长短不一,那就长的大。比如:“ABCD” 比 “ABC”。

记住了,由于在ASCII表里,小写字母比大写字母靠后,所以小写的反倒比大写的大。比如:"aBCD""ABCD"大啊。

 

我这里写个例子,看如何比较字符串:

 

#include <string.h> 

#include <iostream.h>

...

 

int reu = strcmp ("ABCD", "AACD");

 

if (reu > 0)

   cout << "没错, ABCD > AACD" << endl;

else

   cout << "搞错了吧?" << endl;

 

请大家照此例,分别比较 "ABCD" "ABC" "aBCD" "ABCD"

如果你对如何用C++ Builder 建立一个控制台下的工程,请复习第二章第3节。

 

前面说的是英文字母,对于汉字字符串的比较,大小是如何确定的呢?

对于常用汉字,Windows按其拼音进行排序,比如“啊”是最小的,排在最前面,而“坐”之类的,则比较大,排在后面。

对于非常用的汉字,则按笔划来排序。有关常用不常的划分,是国家管的事,我们就不多说了。

 

我一直在网上叫“南郁”,大家可以拿你的名字和我做一下 strcmp,看看谁的名字比较大。(友情提醒:名字大没有什么好处,相反,名字大了,在各种场合里,一般是排名靠后的……)

 

21.5.2.2 字符串复制

 

char *strcpy(char *dest, const char *src);

 

该函数用于将字符串 src的内容,复制给 字符串dest。 注意,一定要保证 dest有足够的空间。

该函数最后返回dest.

 

比如:

char name1[10];

char* name2 = "张三";

 

strcpy (name1, name2);

 

现在name1 的内容也是“张三”。

 

21.6 针数组

学过数组,指针,二者结合起来,指什么?

21.6.1 什么叫“指针数组”?

一个数组用来存放整型数,我们就叫它 整型数组或整数数组;

一个数组用来存入字符,就叫字符数组;

同样,一个数组用来存入指针,那就叫指针数组。

 

比如:

int* p; 这只是一个指针.

int* p[10]; 这是一个数组,里头存放了10个指针。

 

请大家区分:

int* p = new int[10];

int* pa[10];

前者,是一个指针,并且该指针分配了10个元素的空间。

而后者,则是一个指针数组,用于存放10个指针(pa[0],pa[1]...pa[9]都是指针),这10个指针都可以分配10个元素(也可以不是10个,比如是8个或11个)。

 

仍然以建筑来比喻:

 

int* p = new int[10];

p 一张(是的一张而已)图纸,new int[10] 是 我们根据它建了10间房子。

 

int* pa[10];

pa[10] 是10张图纸。至于这10图纸上各准备建多少间房子,我们暂未定下。

 

我们可以通过一个循环,来为pa[10]中的每个指针分配8个元素的空间。

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

{

   pa[i] = new int [8]; //为每个指针都分配8个int元素空间。

}

 

21.6.2 指针数组实例一

 

请打CB,并新建一个控制台工程(记得在出现的对话框中选中“C++选项”)。

然后输入以下代码(部分代码是CB自动生成的,你不必加入):

 

//---------------------------------------------------------------------------

#pragma argsused

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

{

   //定义一个指针数组,可以存放10个int 型指针

   int *p[10];

 

   //循环,为每个指针各分配空间。

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

   {

      p[i] = new int [5];   //分配5个元素的空间

     

      //然后为当前指针中每个元素赋值:

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

         p[i][j] =  j;

   }
    

  //输出每个指针中每个元素(用了两个“每”,所以需要两层循环)

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

  {

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

         cout << p[i][j] << ",";

     cout << endl;
     }

 

  //重要!!!最后也要分别释放每个指针

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

    delete [] p[i];
 

  system("Pause");

  return 0;
   }

//---------------------------------------------------------------------------
 

本例的输出为:

 

21.6.3 指针数组实例二

本例对上例做一些小小改动:

 

//---------------------------------------------------------------------------

#pragma argsused

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

{

   //定义一个指针数组,可以存放10个int 型指针

   int *p[10];

 

   //循环,为每个指针各分配空间。

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

   {

      p[i] = new int [i+1];   //分配i+1个元素的空间

     

      //然后为当前指针中每个元素赋值:

      for (int j = 0; j<i+1; ++j)

         p[i][j] = j;

   }
    

  //输出每个指针中每个元素

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

  {

      for (int j = 0; j<i+1; ++j)

         cout << p[i][j] << ",";

     cout << endl;
     }

 

  //重要!!!最后也要分别释放每个指针

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

    delete [] p[i];
 

  system("Pause");

  return 0;
   }

//---------------------------------------------------------------------------
 

本例的输出为:

 

21.6.4 字符串指针数组

假设我们想在程序中加入某个班级的花名册。让我们来想想如何实现。

由于一个人名由多个字符组成,所以人名就是一个数组。而全班人名,就是数组的数组,因此可以用二维数组来实现。

假设本班只有3个学员,每个学员的人名最长不超过4个汉字,每个汉字占2个字符,加上最后1个固定的结束符,共9个字符来表示一个人名。

 

char names[3][9] =

{

   {"郭靖"},

   {"李小龙"},

   {"施瓦辛格"},

};

 

这是个不错的解决方案。惟一稍有一点不足的是,我们为每个学员分配长9个字符,其实像1号郭靖,他只需5个字符就足矣。但我们使用“二维数组”的方案无法为不同长度的姓名分配不同的空间。

 

这时候,就可以用字符串指针数组了!“锵锵锵”,我们让字符串指针数组的方案出场。

 

char* names[3] =

{

   {"郭靖"},

   {"李小龙"},

   {"施瓦辛格"},

};

 

变化并不多,但是学杂费空间的问题却得到了解决。更为美妙的是,就算现在新来一位大侠叫“无敌鸳鸯腿”,我们也可以从容处理:

char* names[4] =

{

   {"郭靖"},

   {"李小龙"},

   {"施瓦辛格"},

   {"无敌鸳鸯腿"},

};

 

接下来,我们来对“小王成绩管理V2.0”系统做另一种改进。

 

此次改进并不要求可以动态输入学校学生的总数。相反,作来一种“定制版”,我们希望专门为某个学校加入花名册功能,希望在录入成绩时,可以增加显示该学生的姓名。

假设这个学校叫“精武文武学校”,并暂定本期学员也只有上述那4位。

 

"精武馆定制版的小王成绩管理系统"如何实现?呵,我不写了,大家写吧。题目条件就是上面的黑体部分。最终录入界面应类似于:

 

快点开始做吧,把你的作业发在BBS里,让我评评你的成绩是多少 :)))) ?