第二十一章 指针 三 实例演练与提高
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/delete、new[] / 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里,让我评评你的成绩是多少 :))))