5.1 填空
a)指针变量包含另一变量的——值。
b)可以初始化指针的值有——、——或——。
c)可以赋绐指针的惟一整数是——。
5.2判断下列各题是否正确。如果不正确,请说明原因。
a)地址运算符 &只能用于常量、表达式和mgaster存储类声明的变量。
b)声明为void的指针可以复引用。
c)不同类型的指针不必进行强制类型转换操作即可相互赋值。
5.3 问答下列问题。假设单精度浮点数存放在4字节中,数组在内存中的开始地址为1002500。
每道习题尽量使用前面的结果。
a)声明float类型数组numbem,含有10个元素,初始化为0.0、1.1、2.2....9.9。假设符号化常量SIZE定义为10。
b)声明指针nPtr,指向float类型对象。
c)用数组下标符号打印数组numbers的元素。使用for结构,假设声明了整型控制变量i。打印每个数,小数点后面的精度为1。
d)用两条不同语句将数组numbers的开始地址赋给指针变量nPtr。
e)用指针nPtr和指针/偏移量符号打印数组numbers的元素。
f)用数组名作为指针和指针/偏移量符号打印数组numbers的元素。
g)用指针nPtr的下标打印数组numbers的元素。
h)用数组下标符号、数组名作为指针和指针/偏移量符号、nPtr与指针下标符号和nPtr与指针/偏移量符号打印数组numbers的元素4。
i)假设nPtr指向数组numbers开头,nPlr+8指哪个地址,这个地址存放什么值
j)假设nPtr指向numbers[5],执行nPtr-=4之后nPtr引用哪个地址这个地址存放什么值
5.4 对下列各题,各编写一条语句。假设已声明浮点数变量number1和number2,number1初始化为7.3。并假设变量ptr为char*类型,数组s1[100]和s2[100]为char类型。
a)声明变量fPtr为float类型对象的指针。
b)将变量number1的地址赋给指针变量fPtr。
c)打印fPtr所指的对象值。
d)指定fPtr所指对象值为变量number2。
e)打印number2的值。
f)打印number1的地址。
g)打印fPtr中存放的地址,打印的值是否与number1的地址相同
h)将数组s2中存放的字符串复制到数组s1中。
i)比较s1中的字符串与s2中的字符串并打印结果。
j)将s1中字符串中的10个字符添加到s1中的字符串中。
k)确定s1中的字符串的长度。
l)将s2中第一个标记的地址赋给ptr。s2中的标记用逗号(,)分开。
5.5 根据题目要求编写语句。
a)编写函数exchange的函数首部,取两个浮点数x和y的指针为参数,不返回数值。
b)编写a)中函数的函数原型。
c)编写函数evaluale的函数首部,返回整数,取整数x和函数poly的指针参数。函数poly取一个整数参数并返回一个整数。
d)编写c)中函数的函数原型。
e)显示用元音字符串“AEIOU'’初始化字符数组vowel的两种不同方法。
5.6 找出下列程序段中的错误。假设:
char s1[ 50 ] = "jack", s2[ 50 ] = "jill",s3[ 50 ], *sptr; a) cout << strcpy( s3, s2) << endl; b) cout << strcat( strcat( strcpy( s3, s1 ), "and" ), s2) c) cout << strlen( s1 ) + strlen( s2 ) << endl; d) cout << strlen( s3 ) << endl;
5. 7 执行下列语句时打印什么(如果有)如果语句中有错,说明错误及纠正方法。假设声明下列变量:
char s1[ 50 ] = "jack", s2[ 50 ] = "jill",s3[ 50 ], *sptr; a) cout << strcpy( s3, s2) << endl; b) cout << strcat( strcat( strcpy( s3, s1 ), "and" ), s2) c) cout << strlen( s1 ) + strlen( s2 ) << endl; d) cout << strlen( s3 ) << endl;
5.1 a)地址。b)O、NULL或地址。c)O。
5.2 a)不正确。地址运算符只能用于变量,不能用于常量、表达式或用存储类register声明的变量。
b)不正确。void的指针无法复引用,因为无法知道要用多少内存字节复引用。
c)不正确。void类型指针可以赋给其他类型的指针。void类型指针要通过显式地强制类型转换才可以赋给其他类型的指针。
5.3a)float numbers[ SIZE ] = {0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8,9.9}; b) float *nPtr; c) cout << setiosflags ( ios:: fixed | ios:: showpoint << setprecision ( 1 ); for ( i = 0; i < SIZE; i++ ) cout << numbers[ i ] << ' '; d) nPtr = numbers; nPtr = &numbers[ 0 ] ; e) cout << setiosflags ( ios:: fixed | ios:: showpoint << setprecision( 1 ); for ( i = 0; i < SIZE; i++ ) cout << *( nPtr + i ) << ' '; f) cout << setiosflags( ios::fixed | ios::showpoint << setprecision( 1 ); for ( i = 0; i < SIZE; i++ ) cout << *( numbers ) << ' '; g) cout << setiosflags ( ios: :fixed | ios: :showpoint << setprecision( 1 ); for ( i = 0; i < SIZE; i++ ) cout << * ( numbers + i ) << ' '; h) numbers[ 4 ] * ( numbers + 4 ) nPtr[ 4 ] * ( nPtr + 4 )
i) 地址为 1002500 + 8 * 4 = 1002532 值为8.8。
J) numbers[ 5 ]的地址为 1002500 + 5 * 4 = 1002520。
nPtr -= 4的地址为1002500 - 4 * 4 = 1002504
5.4a) float *fPtr; b) fPtr = &number1; c) cout << "The value of * fPtr is "<< *fPtr << endl; d) number2 = *fPtr; e) cout << "The value of number2 is " << number2 << endl; f) cout << "The address of number1 is "<< number1 << endl; g) cout << "The address stored in fPtr is "<< fPtr << endl; h) strcpy(s1,s2); i) cout << "strcmp(s1,s2) =" << strcmp(s1,s2 ) << endl; j) strncat( s1,s2,10 ); k) cout << "strlen(s1) = "<< strlen( s1 ) << endl; l) ptr = strtok( s2, "," );5.5
a) void exchange( float *, float *y ) b) void exchange( float *x, float * ); c) int evaluate( int x, int (*poly) ( int ) ) d) int evaluate( int,int (*)( int ) ); e) char vowe1[ ] = "AEIOU"; char vowe1[ ] = ( 'A', 'E', 'I','O','U','\0' };
5.6 a)不正确:zPtr没有初始化。
纠正:用zPtr = z 初始化zPtr。
b)不正确:指针没有复引用。
纠正:将该语句变成number = *zPtr;
c)不正确:zPtr[2]不是指针,不能复引用。
纠正:将zPtr[2]变为*zPtr[2]
d)不正确:指针下标引用数组界限之外的数组元素。
纠正:将for结构中的关系运算符变为"<"以避免指针下标引用数组界限之外的数组元素。
e)不正确:void指针无法复引用。
纠正:要复引用指针,首先要将其转换为整型指针。将上述语句变为:
number = (int*)sPtr;
f)不正确:指针算法修改数组名。
纠正:用指针变量而不用数组名完成指针算法,或在数组名后面加上下标引用特定元素。
g)不正确:函数strncpy没有将null终止符写入数组s,因为第三个参数等于字符串"hello"的
长度。
纠正:将strncpy的第三个参数变为6或对s[5]赋值'\0',确保在字符串后加上终止null符。
h)不正确:字符数组s太小,不能存放null终止符。
纠正:声明更多元素的数组。
i)不正确:函数strcmp在字符串相等时返回0,因此if结构中的条件为假,不执行输出
语句。
纠正:在if结构条件中将strcmp的结果与0比较。
5.7 a)jill
b)jack and jill
c)8
d)13
5.8 判断对错,并说明原因。
a)比较指向两个不同数组的指针是没有意义的。
b)由于数组名是指向数组第一个元素的指针,因此数组名可以和指针一样进行操作:
5.9 回答下列问题。假设无符号整数存放在2字节中,数组的开始内存地址为1002500。
a)声明5个元素的unsigned int类型数组values,并将其元素初始化为2到10的偶数,假设已经将符号化常量SIZE定义为5。
b)声明指针vPtr,指向unsigned int类型的对象。
c)用数组下标符号打印数组valu~的元素。使用for结构,并假设已经声明整型控制变量i。
d)用两个不同语句将数组values的开始地址指定为指针变量vPlr:
e)用指针/偏移量符号打印数组values的元素。
f)用数组名作为指针和指针/偏移量符号打印数组values的元素。
g)用数组指针的下标打印数组values的元素。
h)用数组下标符号、数组名作为指针和指针/偏移量符号、指针下标符号和指针/偏移量符号引用values的元素5。
i)vPtr+3引用什么地址该地址存放什么值
j)假设vPtr指向values[4],vPtr-=4指向什么地址,该地址存放什么值
5.10 对下列各题,各编写一条语句。假设已声明长整型变量value1和value2,value1初始化为200000。
a)声明变量lPtr为long类型对象的指针。
b)将变量value1的地址赋给指针变量lPtr。
c)打印lPtr所指的对象值。
d)指定lPtr所指对象值为变量value2。
e)打印value2值。
f)打印value1地址。
g)打印lPtr中存放的地址,打印的值是否与value1的地址相同
5.11 根据题目要求编写语句。
a)编写函数zero的函数首部,取长整数数组参数bigIntegers,不返回数值。
b)写出a)中函数的函数原型。
c)编写函数addlAndSum的函数首部,取整数数组参数oneTooSmall并返回一个整数值。
d)写出c)中函数的函数原型。
说明:练习5.12到5.15比较难。完成这些练习后,就可以很容易地实现常见的扑克牌游戏了。
5.12修改图5.24的程序,使洗牌函数向牌手发五张牌,然后编写完成下列任务的函数:
a)确定手中是否有一对牌。
b)确定手中是否有对牌。
c)确定手中是否有三色同号牌(如三张J)。
d)确定手中是否有四色同号牌(如四张A)。
f)确定手中是否有同花(即五张牌花色相同)。
g)确定手中是否有一条龙(即五张牌牌号连续)。
5. 13用练习5. 12建立的函数编写一个程序,发两手五张牌,确定两手牌哪个更好。
5.14修改5.13练习中的程序,模拟发牌器。发牌器的五张牌是盖起来的,游戏者看不到。然后程序求值这手牌,根据牌的质量,抓一张、两张或三张牌,换掉原来手中不要的牌。然后程序重新求值这手牌。注意:这是个难题。
5. 15修改练习5.14的程序,使其能自动处理发牌器中的牌,但游戏者可以确定自己手中要换的牌。然后程序求值两手牌,确定谁赢。用这个新程序与计算机玩20把,看看是你赢还是计算机赢。再让你的朋友与计算机玩20把,看看谁赢得多。根据这些游戏的结果,完善扑克游戏程序(又是个难题)。再与计算机玩20把,修改后的程序是否能够玩更好的游戏
5. 16在图5.24的洗牌与发牌程序中,我们故意用无效的洗牌算法,引入无穷延迟的概念。在这个练习中,要生成高性能的洗牌算法,避免无穷延迟。
按照下面的做法修改图5.24。初始化deck数组(如图5.35)。修改shuffle函数,在数组中一行一行、一列一列地循环,到达每个元素一次。每个元素与随机选择的数组元素进行交换。打印结果数组,确定是否洗好了牌(如图5.36)程序可能要多次调用shuffle函数.才能洗好牌。
注意,尽管这个练习改进了洗牌算法,但洗牌算法仍然要从deck数组搜索第1张牌、第2张牌、第3张牌等等。更糟的是,即使在发牌算法找到并发出牌之后,该算法仍然搜索牌推中的其他元素。修改图5.24的程序,使发牌之后不再继续匹配这张牌,程序立即转入发下一张牌。
5.17 (模拟龟兔赛跑)本练习中要模拟龟兔赛跑的寓言故事。用随机数产生器建立模拟龟兔赛跑的程序。对手从70个方格的第1格开始起跑,每格表示跑道上的一个可能位置,终点线在第70格处。第一个到达终点的选手奖励一个新鲜萝卜和莴苣。兔子要在山坡上睡一觉,因此可能失去冠军。
有一个每秒钟滴答一次的钟,程序应按下列规则调整动物的位置:
动物 运动类型 时间百分比 实际运动 乌龟(tortoise) Fast plod(快走) 50% 向右3格 Slip(跌跤) 20% 向左6格 Slow plod(慢走) 30% 向右1格 兎子(Hare) Sleep(睡觉) 20% 不动 Big hop(大跳) 20% 向右9格 Big slip(大跌) 10% 向左12格 Small hop(小跳) 30% 向右1格 Small slip(小跌) 20% 向左2格
用变量跟踪动物的位置(即位置号1到70)。每个动物从位置1开始,如果动物跌到第1格以外,则移回第1格。
产生随机整数1≤i≤10),以得到上表中的百分比。对于乌龟,1≤i≤5时快走,6≤i≤7时跌跤,8≤i≤10时慢走,兔子也用相似的方法。
起跑时,打印:
BANG !!!!!
AND THEY' RE OFF !!!!!
时钟每次滴答一下(即每个重复循环),打印第70格位置的一条线,显示乌龟的位置T和兔子的位置H。如果两者占用一格,则乌龟会咬兔子,程序从该位置开始打印 OUCH!!!。除T、H和OUCH!!!以外的其他打印位置都是空的。
打印每一行之后,测试某个动物是否超过了第70格,如果是,则打印获胜者,停止模拟。如果乌龟赢,则打印TORTOISE WINS!!!YAY!!!。如果兔子赢,则打印Hare wins.Yush。如果两个动物同时赢,则可以同情弱者,让乌龟赢,或者打印It's a tie。如果两者都没有赢,则再次循环,模拟下一个时钟滴答。准备运行程序时,让一组拉拉队看比赛,你会发现观众有多么投入。
特殊小节:建立自己的计算机
下面几个问题要暂时离开高级语言编程,打开计算机,看看其内部结构。我们介绍机器语言编程和编写几个机器语言程序。要让这些知识更有价值,我们建立一个计算机(通过软件模拟技术),在其中执行我们的机器语言程序。
5.18(机器语言程序)下面要建立一个Simpletron计算机。顾名思义,这是个简单机器,但以后会发现它也是个强大的机器。Simpletron只能运行用Simpletron Machine Language(SML机器语言)写成的程序。
Simpletron包含一个累加器(特殊寄存器),存放Simpletron用于计算和各种处理的信息。Simpletron处理的所有信息都用“字”处理。字是个带符号的四位十进制数,如+3364,-1293,+0007,-0001等等。Simpletron带有100个字的内存.这些字用其内存单元号00、01…99引用。
运行SML程序之前,要先把程序装入内存。每个SML程序中的第一条指令(或语句)一般放在内存单元00处,模拟器会从该地址开始执行。
用SML编写的每条指令都占用Simpletron内存中的一个字。(因此,指令是带符号的四位十进制数)。我们应假设SML指令的符号总是正号,但数据字的符号可正可负。
Simpletron内存中的每个内存单元可以包含一条指令、程序使用的数据值或未用(未定义)内存区。每个SML指令的前两位是操作码,指定要进行的操作。图5.37显示了SML操作码。
SML指令的最后两位是操作数.是要操作的字的特定内存单元。
下面考虑几个简单SML程序。第一个SML程序(例1)从键盘读取两个数.并计算和打印这两个数的和。指令+1007从键盘读取第一个数并将其放在内存单元07(初始化为0),然后指令+1008读取下一个数并将其放在内存单元08。装人命令+2007将第一个数放(复制)到累加器中,加法指令+3008将第二个数与累加器中的数相加。所有SML算术运算指令都把结果留在累加器中。保存指令+2109将结果复制回内存单元09,然后写指令+1109取得并打印这个结果(带符号的四位十进制数),停止指令+4300终止程序执行。
操作码 意义
输入/输出操作:
const int READ = 10 从键盘读一个字到特定内存单元
const int WRITE = 11; 从特定内存单元将字装入累加器
装入/保存操作:
const int LOAD = 20; 从特定内存单元将字装入累加器
const int STORE = 21; 将累加器中的字存放到特定内存单元
算术运算:
const int ADD = 30; 将特定内存单元中的字加上累加器中的字(结果保留在累加器中)
const int SUBTRACT = 31; 将累加器中的字减去特定内存单元中的字(结果保留在累加器中)
const int DIVIDE = 32 将累加器中的字除以特定内存单元中的字(结果保留在累加器中)
const int MULTIPLY = 33; 将特定内存单元中的字乘以累加器中的字(结果保留在累加器中)
控制转移操作:
const int BARANCH = 40; 转移到特定内存单元
const int BRANCHNEG = 41; 在累加器为负值时转移到特定内存单元
const int BRANCHZERO = 42; 在累加器为0时转移到特定内存单元
const int HOLT = 43; 停止,程序已完成任务
图 5.37 SML 机器语言操作码
例1地址 数值 指令 00 +1007 (Read A) 01 +1008 (Read B) 02 +2007 (Load A) 03 +3008 (Add B) 04 +2109 (Store C) 05 +1109 (Write C) 06 +4300 (Halt) 07 +0000 (VAriable A) 08 +0000 (VAriable B) 09 +0000 (Result C)
例2中的SML程序从键盘读取两个数,并确定和打印其中较大的数。注意这里用指令+4107作为打件控制转移,与C++中的if语句相似。
例2地址 数值 指令 00 +1009 (Read A) 01 +1010 (Read B) 02 +2009 (Load A) 03 +3110 (Subtract B) 04 +4107 (Branch negative to 07) 05 +1109 (Write A) 06 +4300 (Halt) 07 +1110 (Write B) 08 +4300 (Halt) 09 +0000 (VAriable A) 10 +0000 (Variable B)
a)用标记控制循环读取10个正数值,计算和打印它们的和。
b)用计数器控制循环读取7个数,有正有负,计算和打印它们平均值。
c)读取一系列数,并确定和打印其最大数。第一个读取的数表示要处理多少个数。
5.19(计算机模拟程序)这里要建立一台计算机,当然不是硬件连接,而是用软件模拟,建立Simpletron的软件模模型。这个Simpletron模拟程序可以将读者的计算机变成Simpletron并可以实际运行。测试和调试练习5.18所编写的SML程序。
这个simpletron模拟程序运行时,开始打印:
*** Welcome to Simpletron! *** *** Please enter your program one instruction *** *** (or data word) at a time. I will type the *** location number and a question mark (?). *** *** You then type the word for that location. *** '** Type the sentinel -99999 to stop entering *** your program. *** 用100个元素的单下标数组memory模拟Simpletron内存。然后假设运行这个simpletron模拟程序,检查一下练习5.18例2的程序: O0 ? +1009 O1 ? +1010 02 ? +2009 03 ? +3110 o4 ? +4107 05 ? +1109 06 ? +4300 07 ? +1110 08 ? +4300 09 ? +0000 10 ? +0000 11 ? -99999 *** Program loading completed *** *** Program execution begins ***
这时SML程序已放进数组memory中,Simpletron开始执行编写的SML程序。程序从内存单元00的指令开始执行,和C++中一样,按顺序继续执行,但可以通过控制转移转入程序的其他部分。
利用变量accumulator表示累加寄存器。用变量counter跟踪内存中包含所执行指令的内存单元。用变量operationCode表示当前正在进行的操作,即指令宇的左边两位。用变量operand表示操作当前指令的内存单元即指令字的右边两位。不要直接从内存中直接执行指令,而是将下一个要执行的指令从内存中转到变量instructionRegister中。然后选取左边两位,放进operationCode中,选取右边两位,放进operand中。Simpletron开始执行时,所有特殊寄存器都初始化为0。
下面看看第一个SML指令(内存地址00的指令+1009)的执行过程,这是条指令执行循环(instruction execution cycle)。
counter指示下一个要执行指令的内存单元。我们用下列C++语句从memory中取得该地址的内容:
instructionRegister = memory[counter];
下列语句从指令寄存器读取操作码和操作数:
operationCode = instructionRegister / 1OO; operand‘= instructionRegister % 100;
现在Simpletron必须确定操作码是读(而不是写、装入等等)。switch结构区分SML的十二种操作。
在switch结构中,各种SML操作指令的模拟如下(其他留给读者练习):
读(read): cin>>memory [ operand ];
装入(load): accumulator= memory[operand];
加(add): accumulator += memory[operand];
转移(branch): 稍后介绍转移
停止(halt): 这条指令打印下列消息
*** Simpletron excution terminated ***
然后打印每个寄存器的名称与内容即内存的完整内容。这种打印输出称为计算机转储(computer dump)。为了帮助编制转储功能,图5.38显示了一个示例转储格式。注意执行Simpletron程序之后,计算机转储显示指令实际值和终止执行时的数据值。
下面继续执行我们程序的第一条指令,内存单元00中的+1009。前面曾介绍过,switch语句用下列C++语句模拟这个过程:
cin >> memory[ operand ] ;
执行cin之前,屏幕上显示一个问号(),提示用户输入。Simpletron等待用户输入一个值并按Return键。然后将这个值读取到内存单元09。
这时,第一条指令模拟已经完成。余下的就是准备让Simpletron执行下一条指令。由于刚刚完成的指令不是控制转移,因此只要增加指令计数器寄存器,如下所示:
++counter;
这就完成了第一条指令的模拟执行。整个过程(即指令执行循环)重新开始,读取下一个要执行的指令。
现在考虑如何模拟分支结构(控制转移),只要调整指令计数器的值即可。因此,无条件转移指令(40)可以用switch模拟如下:
counter = operand;
条件(累加器为0则转移)指令模拟如下:
if( accumulator == O ) counter = operand;
这时就可以实现Simpletron模拟程序和运行练习5.18中编写的每个程序了。可以在SML中增加其他特性,并在Simpletron模拟程序中提供这些特性。
Simpletron模拟程序应检查各种错误。例如,程序装入期间,用户输入Simpletron程序的memory中的每个数应在-9999到+9999之间。Simpletron模拟程序应当用while循环测试输入的每个数值是否在这个范围中,如果不是,则提示用户重新输入,直到输入正确的值。
REGISTERS:
accumulator +0000
counter 00
instructionRegister +0000
operationCode 00
operand 00
MEMORy:
0 1 2 3 4 5 6 7 8 9
0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
10 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
20 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
40 +0000 +0000 *0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
50 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
6O +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
90 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
图 5.38示例转储
在执行过程中,Simpletron模拟程序应检查各种严重错误,如除数为0、执行无效操作码、累加器溢出(即算术运算的结果不在-9999到+9999之间)等等。这种严重错误称为致命错误。发现致命错误时,Simpletron模拟程序应打印下列错误消息:
*** Attempt to divide by zero ***
Simpletron execution abnormally terminated ***
并按前面介绍的格式打印完整的计算机转储,这样可以帮助用户找到程序中的错误。
更多的指针练习
5.20修改图5.24的洗牌与发牌程序,使洗牌与发牌操作由同一函数(shuffleAndDeal)完成。这个函数应包含嵌套循环结构,类似于图5.24中的函数shuffle。
5.21下列程序有什么作用
// ex05_21.cpp #include<iostream.h> void mystery1(car *,const char *); int main() { char string1[80],string2[80]; cout<<"Enter two strings:"; cin>>string1>>string2; mystery1(string1,string2); cout << string1 << endl; return 0; } void mystery1( char *s1, const char *s2 ) { while { *s1 != '\0' } ++s1; for ( ; *s1 = *s2; s1++, s2++ ) ; // empty statement }
5.22下列程序有什么作用
// ex05 22.cpp #includevoid mystery2( const char * ); int main() { char string[ 80 ]; cout << "Enter two strings: "; cin >> string; cout << mystery2( string ) << endl; return 0; } int mystery2( const char *s ) { for {int x = O; * s != '\0'; s++ ) ++x; return x; }
5.23找出下列程序中的错误。如果能纠正,请说明纠正方法。
5.24(快速排序)在第4章的例子和练习中,我们介绍了冒泡排序、桶排序和选择排序:现在要介绍称为快速排序的递归排序方法。单下标数组值的基本算法如下:
a)分区步骤:取未排序数组的第一个元素,确定其在排序数组中的最终位置,即该元素左边的所有值小于该元素,该元素右边的所有值大于该元素。这样就确定了一个元素位置,有了两个未排序子数组。
b)递归步骤:对每个未排序子数组完成第一步。
每次对未排序子数组完成第一步时,又确定了一个元素位置,有了另外两个未排序子数组。当子数组只有一个元素时,就已经排序完毕,该元素已经在最终位置了。
基本算法似乎很简单,但如何确定每个子数组的第一个元素在排序数组中的最终位置呢例如,考虑下列数值(黑体元素是分区元素,要放在排序数组中的最终位置)
37 2 6 4 89 9 10 12 68 45
a)从数组最右边的元素开始,比较37与每个元素,直到找出小于37的元素.然后将这个元素与37交换。第一个小于37的元素是12.因此将12与37交换。新数组如下:
12 2 6 4 89 8 lO 37 68 45
元素12用斜体表示刚刚与37交换。
b)从数组左边12以后的元素开始,比较37与每个元素,直到找出大于37的元素,然后将这个元素与37交换。第一个大于37的元素是89,因此将89与37交换。新数组如下:
12 2 6 4 37 8 lO 89 68 45
c)从数组右边89以前的元素开始,比较37与每个元素,直到找出小于37的元素,然后将这个元素与37交换。第一个小于37的元素是10,因此将10与37交换。新数组如下:
12 2 6 4 10 8 37 89 68 45
d)从数组左边10以后的元素开始,比较37与每个元素,直到找出大于37的元素,然后将这个元素与37交换。由于没有比37更大的元素,因此37已经放在排序数组中的最终位置。
对上述数组采用分区后,就出现两个未排序小数组。小于37的未排序小数组包含12、2、6、4、10和8。大于37的未排序小数组包含89、68和45。对这两个未排序小数组进行像原数组一样的处理。
根据上述介绍,编写一个递归函数quiksort,排序单下标整型数组。函数接收—个整型数组、开始下标和结束下标参数。quicksort调用函数Partition函数进行分区。
5.25(走迷宫)下列#和圆点(.)组成的网格是表示迷宫的双下标数组。
# # # # # # # # # # # # # . . . # . . . . . . # . . # . # . # # # # . # # # # . # . . . . # . # # . . . . # # # . # . . # # # # . # . # . # . # # . . # . # . # . # . # # # . # . # . # . # . # # . . . . . . . . # . # # # # # # # . # # # . # # . . . . . . # . . . # # # # # # # # # # # # #
上述双下标数组中,#表示迷宫的墙,圆点表示可以走的路线,只能在数组中包含圆点 的地方移动。
走迷宫的一个简单算法总能走到出口(如果有)。如果没有出口,则会回到起始点。
将右手放在右边的墙上并开始前进,手不离墙,最终总能走到出口。当然,可能还有更短的路径,但上述路径总能走到出口。
编写走迷宫的递归函数mazeTraverse。函数接收12 x 12字符的数组,表示这个迷宫,并接收开始位置参数。mazeTraverse在寻找迷宫的出口时,应将字符x放在沿途的每一格。
每次移动之后,函数应显示迷宫,让用户能看到迷宫的走法。
5.26(随机产生迷宫)编写一个mazeGenerator函数,接受12 x 12字符的数组并随机产生迷宫。函数还应提供迷宫的开始和结束位置。试用随机产生迷宫测试练习5.25所写的函数mazeTraverse。
5 27(任何大小的迷宫)将练习5.25和5.26的函数mazeTraverse与mazeGenerator一般化,可以处理任伺大小的迷宫。
5.28(函数指针数组)将图4.23的程序改写成使用菜单驱动界面。程序提供5个选项如下所示(应在屏幕上显示):
Enter a choice:
0 Print the array of grades
1 Find the minimum grade
2 Find the maximum grade
3 Print the average on all tests for each student
4 End program
和返回相同类型数值的函数。因此,图4.23的函数应修改成接收相同类型参数和返回相
同类型数值。将函数minimum和maximum修改成打印最小值与最大值,不返回任何内容。
对选项3,将图4.23的函数average修改成输出每个学生(而不是特定学生)的平均成绩。
函数average与函数printArray、minimum和maximum接收相同类型参数且不返回任何内
容。将4个函数的指针存放在数组processGrades中,并用用户选择的选项作为调用每个
函数的数组下标。
5.29(修改Simpletron模拟程序)练习5.19中编写了计算机的软件模拟,执行用Simpletron Machine Language(SML)编写的程序。本练习中要对Simpletron模拟程序进行几个修改和补充。练习15.26和15.27中将建立一个编译器,将高级编程语言(BASIC的变形)编写的程序转换为SML。要执行编译器产生的程序.需要进行下列修改和补充。
a)将Simpletron模拟程序的内存扩展为包含10410个内存单元,使Simpletron模拟程序能处理更大的程序。
b)让模拟程序进行求模计算,这要求增加SML指令。
c)让模拟程序进行指数计算,这要求增加SML指令。
d)将Simpletron模拟程序修改成用十六进制值而不是用整数值表示SML指令。
e)将Simpletron模拟程序修改成允许输出换行符。这要求增加SML指令。
f)将Simpletron模拟程序修改成不仅能处理整数值,而且能处理浮点数。
s)将Simpletron模拟程序修改成处理字符串输入。提示每个Simpletron字可以分为两组,各放两位整数。每个两位整数表示一个字符的ASCII十进制的对应值。增加在特定的 Simpleton内存单元开始输入和存放字符串的机器语言指令。该内存单元的字的前半部
分是字符串中的字符数(即字符串的长度),后半部分包含一个用两个十进制位所表示
的ASCII字符。机器语言指令将每个字符变为对应ASCII值并赋给半个字。
h)将Simpletron模拟程序修改成处理字符串输出(按g)中的格式存放)。提示:增加在特
定Simpletron内存单元开始打印字符串的机器语言指令。该内存单元的字的前半部分是
字符串中的字符数(即字符串的长度),后半部分包含一个用两个十进制位表示的ASCll字
符。机器语言指令将每个两位数变为对应字符,检查字符串长度,并通过将两位数转
换成相应字符来打印字符串。
5.30 下列程序有什么作用
// exO5_30.cpp
#include <iostream.h>
int mystery3( const char *, const char * );
int main()
{
char string1[ 80 ], string2[ 80 ];
cout << "Enter two strings: ";
cin >> string1 >> string2;
cout << "The result is"
<< mystery3< string1, string2 << endl;
return 0;
}
int mystery3 ( const char *s1, const char *s2 }
{
for ( ; * s1 != '\0' && *s2 != '\0'; s1++,s2++ )
if ( *s1 != *s2 )
return 0;
return 1;
}
字符串操作练习
5.31编写一个程序,用函数strcmp比较用户输入的两个字符串。程序指出第一个字符串是小于、等于或大于第二个字符串。
5.32编写一个程序,用函数strncmp比较用户输入的两个字符串,程序要输入比较的字符数。程序指出第一个字符串是小于、等于或大于第二十字符串。
5.33编写一个程序,用随机数产生器建立语句。程序用4个char类型的指针数组article、noun、verb和preposmon。程序按下列顺序从4个数组分别随机取一个元素生成语句:article、noun、verb、preposmon、article和noun。选择每个单词时,在能放下整个句子的数组中连接上述单词。单词之间用空格分开。输出最后的语句时,应以大写字母开头,以圆点结尾。程序产生20个句子。
数组填充如下:article数组包含冠词”the”、”a”、”One”、”some”和”any”,“noun数组包含名词””,”girl”、”dog”、”town”和”car”,verb数组包含动词”drove”、”jumped”、 ”ran”、”walked”和”skipped”,preposmon数组包含介词”to”、”from"、”over”、”under',和“on”。
编写上述程序之后,将程序修改成产生由几个句子组成的短故事(这样就可以编写一篇自动文章)。
5.34(五行打油诗)五行打油诗由五句话组成,第一行、第二行与第五行压韵,第三行与第四行压韵。利用练习5.33介绍的方法,编写一个随机产生五行打油诗的C++程序。要产生好的五行打油诗并不容易,但这个工作非常有趣。
5.35编写一个将英语短语编成pig Latin的程序,pigLatin就是故意打乱单词的字母顺序。下面是一个简单的pigLatin算法。
要将英语短语编成pig Latin.用函数strtok将短语标记化为各个单词:要把单词变成pig Latin,将第一个字母放到末尾,并加上“ay'’字样.如"jump"变成”umpjay”,“the”变成“hetay”,“computer”,变成“omputercay"。单词之间的空格保持不变。假设单词之间用空格分开,没有标点符号,每个单词均由两个以上字母组成。函数printLatinWord显示每个单词。提示每次调用strtok并找到一个标记时,将标记指针传递给函数print Latin
Word,井打印pigLatin单词。
5.36编写一个程序,以(555)555—5555形式输入电话号码字符串。程序用函数strtok取得区号标记,电话号码的前三位作为一个标记,后四位作为另一个标记。电话号码的七位数连接成一个字符串。程序将区号字符串变为int型,将电话号码字符串变为long型,并打印区号和电话号码字符串。
5.37编写一个程序,输入一行文本,用strtok函数标记化该行文本,并以相反顺序输出标记。
5.38用5.12.2节介绍的字符串比较函数和第4章介绍的数组排序技术编写一个程序,按字母顺序列出字符串清单。
用10个或15个城市名作为程序数据。
5.39对图5.29的字符串复制和字符串连接函数编写两个版本,—个用数组下标,一个用指针与指针算法。
5.40对图5.29的字符串比较函数编写两个版本,一个用数组下标,一个用指针与指针算法。
5.41对图5.29的字符串strlen函数编写两个版本,一个用数组下标,一个用指针与指针算法。
上述练习是本书的重点,用于测试读者对基本字符串操作概念的理解。本节介绍一组中高级字符串操作练习,这些题目的难度不一,有的要花一两个小时来编写和实现,有的要两三周来进行实验室讨论和实现,有些是较难的小组项目。
5.42(文本分析)利用计算机的字符串操作功能可以用非常有趣的方法分析大作家的写作方法。许多人在分析莎士比亚是否真有其人,有些学者认为。许多重要证据表明,莎土比亚实际上是Christopher Marlowe或其他作家的化名。研究人员通过计算机寻找这些作家在写作中的相似性。本练习介绍三种用计算机分析文章的方法。
a)编写一个程序,从键盘读取几行文本,并打印一个表格,显示文中字母的出现次数。例如,下列短语:
To be,or not to be: that is the question:
包含一个a、二个b、不包含c等等。
b)编写一个程序,从键盘读取几行文本,并打印一个表格,显示文中单字符单词、双字符单词、三字符单词等的出现次数。例如,下列短语:
Whether 'tis nobler in the mind tO suffer
包含:
字长 出现次数 1 0 2 2 3 2 4 2(包括'tis) 5 0 6 2 7 1
c)编写一个程序,从键盘读取几行文本,并打印一个表格,显示文中每个单词的出现次数。程序的第一个版本应在表中按文中出现的顺序列出单词。例如,下列语句:
To be, or not to be:that is the question:
Whether 'tis nobler in the mind to suffer
包含三个“to”、两个“be”、一个“or”等等。然后按字母顺序列出更有趣(也更有用)的打印输出。
5.43(字处理)字处理系统的一个重要功能是输入对齐,将单词与页面的左右边界对齐,从而产生漂亮的文档,就像是经过排版的,而不是直接输入的。计算机系统通过在行中的单词之间插入空格而让最右边的单词与右边界对齐。
编写一个程序,读取几行文本.并按对齐格式打印这些文本。假设文本在8.5英寸宽的纸上打印.左右页边留下1英寸的边距。假设计算机在水平方向每英寸打印10个字符。
因此,可以在6.5英寸的空间打印65个字符。
5.44(按不同格式打印日期)可以按不同格式打印日期,两种常用格式如下所示:
07/21/55 July 2l。1955
编写一个程序,读取第一种日期格式,并打印第二种日期格式。
5.45(支票保护)计算机经常用于工资与账号支付应用等支票写入系统。许多怪事常常出现,如每周工资支票上错误地多写1百万美元。由于人为和机器的错误,使支票写入系统写出不正常的数值。系统设计人员在系统中建立控制,防止发出这种错误支票。
另一个严重问题是有些人故意改变支票金额,想窃取钱财。要防止改变支票金额,大多数支票写入系统采用支票保护(check protection)技术。
99.87 --------- 12345678
包含三个空格。如果打印支票时留下空格,则很容易篡改。要防止改变支票金额,许多支票写入系统插入如下星号:
***99.87 ----------- 12345678
编写一个程序,输入支票上要打印的美元数,然后用支票保护格式打印金额,必要时加上星号,假设用九个空格打印金额。
5.46(写出支票金额的大写)继续上面的例子,设计支票写入系统以防止改变支票金额非常重要,一个常用的安全方法是写出支票金额的大写。即使支票的数字好改,大写金额也很难篡改。
许多计算机支票写入系统不写出支票金额的大写,也许主要原因是商用应用程序使用的大多数高级语言没有足够的字符串操作特性,另一个原因则是编写大写金额的程序比较复杂。
编写一个C++程序,输人数字金额,输出大写金额。例如,金额112.43写成:
ONE HUNDRED TWELVE and 43/100
5.47(莫尔斯码)也许最著名的编码机制是莫尔斯码,是1832年由Samuel Morse创立的,用于电报系统使用。莫尔斯码对字母、数字和一些特殊符号(如圆点、逗号、分号)指定一系列点和线。在面向声音的系统中,点表示短音,线表示长音。点线表示还用于面向光的系统和面向信号标志系统。
单词之间用空格分开,没有点和线。在面向声音的系统中,空格表示为短时间不发声音。图5.39显示了莫尔斯码的国际化版本。
编写一个程序,读取一句英语短语,并将其编制成莫尔斯码,再用一个程序将莫尔斯码变成英语。莫尔斯码编码字母之间用一个空格,莫尔斯码编码单词之间用三个空格。
计算机打印的支票包含固定的可以打印金额的空格。假设支票中用八个空格写入每周工资,如果金额太大,则八个空格都会填满,例如:
1,230.60 (支票金额) ---------- 12345678 (位置号)
另一方面,如果金额少于1000美元,则有些空格空着。例如:
------------------------------------------------------------------- 字符 代码 字符 代码 ------------------------------------------------------------------- A .- T - B -... U ..- C -.-. V ...- D -.. W .-- E . X -..- F ..-. Y -.-- G --. Z --.. H .... I .. 数字 J .--- 1 .---- K -.- 2 ..--- L .-.. 3 ...-- M -- 4 ....- N -. 5 ..... O --- 6 -.... P .--. 7 --... Q --.- 8 ---.. R .-. 9 ----. S ... 0 ----- ------------------------------------------------------------------ 图 5.39 莫尔斯码的国际化版本
5.48(公制换算程序)编写一个程序,帮助用户进行公制换算。程序让用户指定单位名字符串(如centimeter、liter、gram等表示公制,inche、quart、pound等表示英制),并回答下列简单问题:
”How many inches are in 2 meters”
”How many liters are in 1O quarts”
程序应能认识无效转换。例如:
”How many feet in 5 kilograms”
是无意义的,因为”feet”是长度单位,而”kilograms”是重量单位。
5. 49(纵横填字谜产生器)许多人都玩过纵横填字谜游戏,但很少人建立过纵横填字谜。建立纵横填字谜是个复杂的问题,这是个复杂字符串操作项目。程序员即使建立最简单的纵横填字谜,也要解决大量问题。例如,如何在计算机中表示纵横填字谜的网格是用一系列字符串,还是用双下标数组程序员要提供程序直接引用的单词源(即计算机化字典)。这些单词以怎样的形式存放来实现程序所需的复杂操作有的读者还想建立纵横填字谜的“线索”,提示每行每列要打印的单词。仅仅打印一个空的纵横填字谜就不是一件简单的事。