面向对象程序设计课后习题答案.docx
《面向对象程序设计课后习题答案.docx》由会员分享,可在线阅读,更多相关《面向对象程序设计课后习题答案.docx(74页珍藏版)》请在冰豆网上搜索。
面向对象程序设计课后习题答案
第一章:
面向对象程序设计概述
[1_1]什么是面向对象程序设计?
面向对象程序设计是一种新型的程序设计范型。
这种范型的主要特征是:
程序=对象+消息。
面向对象程序的基本元素是对象,面向对象程序的主要结构特点是:
第一:
程序一般由类的定义和类的使用两部分组成,在主程序中定义各对象并规定它们之间传递消息的规律。
第二:
程序中的一切操作都是通过向对象发送消息来实现的,对象接受到消息后,启动有关方法完成相应的操作。
面向对象程序设计方法模拟人类习惯的解题方法,代表了计算机程序设计新颖的思维方式。
这种方法的提出是软件开发方法的一场革命,是目前解决软件开发面临困难的最有希望、最有前途的方法之一。
[1_2]什么是类?
什么是对象?
对象与类的关系是什么?
在面向对象程序设计中,对象是描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。
对象可以认为是:
数据+操作
在面向对象程序设计中,类就是具有相同的数据和相同的操作的一组对象的集合,也就是说,类是对具有相同数据结构和相同操作的一类对象的描述。
类和对象之间的关系是抽象和具体的关系。
类是多个对象进行综合抽象的结果,一个对象是类的一个实例。
在面向对象程序设计中,总是先声明类,再由类生成对象。
类是建立对象的“摸板”,按照这个摸板所建立的一个个具体的对象,就是类的实际例子,通常称为实例。
[1_3]现实世界中的对象有哪些特征?
请举例说明。
对象是现实世界中的一个实体,其具有以下一些特征:
(1)每一个对象必须有一个名字以区别于其他对象。
(2)需要用属性来描述它的某些特性。
(3)有一组操作,每一个操作决定了对象的一种行为。
(4)对象的操作可以分为两类:
一类是自身所承受的操作,一类是施加于其他对象的操作。
例如:
雇员刘名是一个对象
对象名:
刘名
对象的属性:
年龄:
36生日:
1966.10.1工资:
2000部门:
人事部
对象的操作:
吃饭开车
[1_4]什么是消息?
消息具有什么性质?
在面向对象程序设计中,一个对象向另一个对象发出的请求被称为“消息”。
当对象接收到发向它的消息时,就调用有关的方法,执行相应的操作。
消息是一个对象要求另一个对象执行某个操作的规格的说明,通过消息传递才能完成对象之间的相互请求或相互协作。
消息具有以下3个性质:
(1)同一个对象可以接收不同形式的多个消息,做出不同的响应。
(2)相同形式的消息可以传递给不同的对象,所做出的响应可以是不同的。
(3)消息的发送可以不考虑具体的接收者,对象可以响应消息,也可以不响应。
[1_5]什么是方法?
消息和方法的关系是什么?
在面向对象程序设计中,要求某一对象作某一操作时,就向该对象发送一个响应的消息,当对象接收到发向它的消息时,就调用有关的方法,执行响应的操作。
方法就是对象所能执行的操作。
方法包括界面和方法体两部分。
方法的界面也就是消息的模式,它给出了方法的调用协议;方法体则是实现某种操作的一系列计算步骤,也就是一段程序。
在C++语言中方法是通过函数来实现的,称为成员函数。
消息和方法的关系是:
对象根据接收到的消息,调用相应的方法;反过来,有了方法,对象才能响应相应的消息。
[1_6]什么是封装和抽象?
请举例说明。
在现实世界中,所谓封装就是把某个事物包围起来,使外界不知道该事物的具体内容。
在面向对象程序设计中,封装是指把数据和实现操作的代码集中起来放在对象内部,并尽可能隐蔽对象的内部细节。
对象好象是一个不透明的黑盒子,表示对象属性的数据和实现各个操作的代码都被封装在黑盒子里,从外面是看不见的,更不能从外面直接访问或修改这些数据及代码。
使用一个对象的时候,只需要知道它向外界提供的接口形式而无需知道它的数据结构细节和实现操作的算法。
封装机制可以将对象的使用者与设计者分开,使用者不必知道对象行为实现的细节,只需要使用设计者提供的接口让对象去做。
抽象是人类认识问题的最基本的手段之一。
它忽略了一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。
抽象是对复杂世界的简单表示,抽象强调感兴趣的信息,忽略了不重要的信息。
例如,设计一个学籍管理程序的过程中,考察某个学生对象时,只关心他的姓名、学好、成绩等,而对他的身高、体重等信息就可以忽略。
以一般观点而言,抽象是通过特定的实例(对象)抽象共同性质以后形成概念的过程。
抽象是对系统的简化描述或规范说明,它强调了系统中的一部分细节和特性,而忽略了其他部分。
抽象包括两个方面:
数据抽象和代码抽象(或称为行为抽象)。
前者描述某类对象的属性或状况,也就是此类对象区别于彼类对象的特征物理量;后者描述了某类对象的共同行为特征或具有的共同操作。
在面向对象程序设计方法中,对一个具体问题的抽象分析的结果,是通过类来描述和实现的。
现在以学生管理程序为例,通过对学生进行归纳、分析,抽取出其中的共性,可以得到如下的抽象描述:
共同的属性:
姓名、学号、成绩等,他们组成了学生数据抽象部分。
用C++语言的数据成员来表示,可以是:
char*name;intnumber;floatscore;
共同的行为:
数据录入、数据修改和数据输出等,这构成了学生的行为抽象部分,用C++语言的成员函数表示,可以是:
input();modify();print();如果我们开发一个学生健康档案程序,所关心的特征就有所不同了。
可见,即使对同一个研究对象,由于所研究问题的侧重点不同,就可能产生不同的抽象结果。
[1_7]什么是继承?
请举例说明。
继承所表达的是对象类之间的相关关系,这种关系使得某类对象可以继承另一类对象的特征和能力。
现实生活中,继承是很普遍和容易理解的。
例如我们继承了父母的一些特征,如种族、血型、眼睛的颜色等,父母是我们所具有的属性的基础。
继承所表达的是对象之间相关的关系。
这种关系使得某一类可以继承另一个类的特征和能力。
[1_8]若类之间具有继承关系,则它们之间具有什么特征?
(1)类间具有共享特征(包括数据和操作代码的共享)
(2)类间具有差别或新增部分(包括非共享的数据和代码操作)
(3)类间具有层次结构
假设有两个类A和B,若类B继承类A,则类B包含了类A的特征(包括数据和操作),同时也可以加入自己所特有的新特性。
这时,我们称被继承类A为基类或父类或超类;而称继承类B为A类的派生类或子类。
同时,我们还可以说,类B是从类A中派生出来的。
[1_9]什么是单继承、多继承?
请举例说明。
从继承源上分,继承分为单继承和多继承。
单继承是指每个派生类只直接继承了一个基类的特征。
多继承是指多个基类派生出一个派生类的继承关系。
多继承的派生类直接继承了不止一个基类的特征。
例如:
小孩的玩具车继承了车的一些特性,还继承了玩具的一些特征。
[1_10]什么是多态性?
举例说明。
多态性也是面向对象程序设计的重要特性。
它是指不同的对象收到相同的消息时产生不同的行为方式。
例如我们同样双击windows系统桌面上的图标时,有的是打开多媒体播放器,有的是打开资源管理器。
利用多态性,用户只需发送一般形式的消息,而将所有的实现留给接收消息的对象。
对象根据所收到的消息做出相应的动作。
[1_11]什么是函数重载和运算符重载?
为什么要使用重载?
重载一般包括函数重载和运算符重载。
函数重载是指一个表示符可同时用于为多个函数命名,而运算符重载是指一个运算符可同时用于多种运算。
也就是说,相同名字的函数或运算符在不同的场合可以表现出不同的行为。
使用重载的目的是为了更好地表达行为共享,这种行为共享就象将相似的操作划分在一起。
使用重载可以使程序员在只知道操作的一般含义,而不知道操作的具体细节的情况下能正确地对某个对象使用一个操作。
另外,使用重载的直接益处是减少了程序员记忆操作的名字的负担。
第二章:
:
C++基础
[2_1]简述C++的主要特点
(1)C++保持与C的兼容,用C编写的软件可以用到C++中。
(2)用C++编写的程序可读性好,代码结构更合理,可直接地在程序中映射问
题空间的结构。
(3)生成代码的质量高。
(4)软件的可重用性、可扩充性、可维护性和可靠性有了明显的提高,从而节省了开发费用和时间。
(5)支持面向对象的机制,可方便地构造出模拟现实问题的实体和操作。
[2_2]下面是一个C程序,改写它,使它采用C++风格的i/o语句
改写如下:
#include
main()
{inta,b,d,min;
cout<<”entertwonumbers:
“;
cin>>a;
cin>>b;
min=a>b?
b:
a;
for(d=2;dif((a%b)==0)&&((b%d)==0))break;
if(d==min)
{cout<<”nocommondenominators\n”;
return0;
}
cout<<”thelowestcommondenominatoris“<return0;
}
[2_3]测试下面的注释是否有效?
此注释有效,单行注释中可以嵌套/*……….*/方式的注释。
[2_4]以下简单的C++程序不可能编译通过,为什么?
原因是:
在程序中,当一个函数的定义在后,而对它的调用在前时,必须将该函数的原型写在调用语句之前,而在本程序中缺少函数原型语句。
在语句:
#include后加上语句sum(inta,intb);就可以通过了。
[2_5]
(1)答:
这两个函数原形是等价的,因为函数原型中的参数名可以缺省。
(2)答:
这两个函数的第一行是不等价的,函数的第一行中必须包含参数名。
(3)答:
这两个函数原型是等价的,因为在函数原型中未注明参数,C++认为该函数的参数表为空(void)
[2_6]答:
输出结果为:
1020因为f函数的参数是引用,所以修改k的值有效。
函数调用后,主函数中k的值变为10。
由于m是对函数的引用,当m被赋
值为20时,k的值也变为20。
[2_7]举例说明可以使用const替代#define以消除#define的不安全性
答:
例如:
#include
#defineA2+4
#defineBA*3
voidmain()
{cout<
上面程序的运行结果是14而不是18,但很容易被认为是18。
用const替代#define就能得到正确结果,从而消除了#define的不安全性。
#include
constA=2+4;
constB=A*3;
voidmain()
{cout<
运行结果为18。
[2_8]答:
使用内联函数的优点主要有两个:
一是能加快代码的执行,减少调用
开销;二是能消除宏定义的不安全性。
[2_9]用动态分配空间的方法计算Fibonacci数列的前20项并存储到动态分配的
空间中。
答:
#include
#include“stdio.h”
voidmain()
{intI,*p=newint[20];//动态分配20个整型空间
*p=1;
*(p+1)=1;//前面两个空间赋值1
cout<<*p<<”\t”<<*(p+1)<<”\t”;
p=p+2;//p指向第三个空间
for(i=3;i<=20;i++)
{*p=*(p-1)+*(p-2);
cout<<*p<<”\t”;
if(i%5==0)cout<p++;//指向下一个空间
}
}
结果:
11235
813213455
89144233377610
9871597258441816765
[2_10]建立一个被称为sroot()的函数,返回其参数的二次方根。
重载sroot()三次,
让它返回整数、长整数与双精度数的二次方根(计算二次方根时,可以使用标准
库函数sqrt())
#include
#include
intsroot(int);longsroot(long);doublesroot(double);
doublesqrt();//声明开方函数sqrt()
voidmain()
{inti,x;longl,y;doubled,z;
cin>>i;cin>>l;cin>>d;
x=sroot(i);y=sroot(l);z=sroot(d);
cout<}
intsroot(inti)
{returnsqrt(i);}//i是整数
longsroot(longl)
{returnsqrt(l);}//l是长整型
doublesroot(doubled)
{returnsqrt(d);}//d是双精度
//敲进91625
//输出345
习题[2_11]编写C++风格的程序,解决百钱问题,将一元人民币兑换成1、2、5分的硬币,有多少种换法?
#include
voidmain()
{inti,j,sum=0;
for(i=0;i<=20;i++)
for(j=0;j<=50;j++)
if(100-5*i-2*j>=0)
{
sum++;
cout<<100-5*i-2*j<<”\t”<}
cout<<”sumis“<}
习题[2_12]编写C++风格的程序,用二分法求解f(x)=0的根
#include
#include
inlinefloatf(floatx)
{return2*x*x*x-4*x*x+3*x-6;}
voidmain()
{floatleft,right,middle,ym,yl,yr;
cout<<”pleasstwonumber:
”<cin>>left>>right;
yl=f(left);
yr=f(right);
do
{middle=(right+left)/2;
ym=f(middle);
if(yr*ym>0)
{right=middle;
Yr=ym;
}
else
{left=middle;
yl=ym;
}
}while(fabs(ym)>=1e-6);
cout<<”\nRootis:
”<}
本例使用了内联函数f(x),因为在主函数中多次调用它,这样可以加快代码执行的速度。
敲进两个数:
-1010结果:
Rootis2
[2_13]答:
运行结果是:
2461210说明:
本例使用的是返回引用的值,index(3)=12;语句的执行实际将a[3]赋值为12。
[2_14]答:
运行结果为:
101说明:
在语句:
:
i=i+1;中赋值号左边:
:
i的i单元是全
局变量,赋值号右边的i单元是局部变量i。
所以执行该语句的结果是
将局部变量i的值+1(101)赋值给全局变量i
[2_15]答:
结果是:
1010说明:
函数f(&a,b)中的第一个参数是引用,引用参数是一种按地址传递参数的方法,对其的调用是传地址调用;而第二个参数是变量参数,对它的调用是通常的传值调用。
所以运行后,a的值被改为10,b的值不变,仍为10
[2_16]答:
D
说明:
int*p=newint(10);表示分配1个整型空间,初值为10
int*p=newint[10];表示分配10个整型空间
int*p=newint;表示分配1个整型空间
int*p=newint[10](0)想给一个数组分配内存空间时,对整个数组进行初始化,这是不允许的。
[2_17]答:
D说明:
name被定义为指向常量的常指针,所以它所指的内容和本
身的内容都不能修改,而name[3]=’a’;修改了name所指的
常量,name=’lin’;和name=newchar[5];修改了常指针,只有
D输出一个字符是正确的。
[2_18]答:
A说明:
name被定义指向常量的指针,这是一个不能移动的固定指针,它所指的内容不能改变,但指针所指的数据可以改变,而name[3]=’q’;修改了name所指的内容,是正确的。
name=”lin”;name=newchar[5];name=newchar(‘q’);以不同的方法修改了常指针,都是错误的。
[2_19]答:
A说明:
name被定义指向常量的指针,不允许改变指针所指的常量,但指针本身的内容可以修改,而name[3]=’q’;修改了name所指的内容,是错误的。
name==”lin”name=newchar[5];和name=newchar(‘q’)以不同的方法修改了常指针,都是正确的。
[2_20]答:
D说明:
C++中不能建立引用数组和指向引用的指针,也不能建立引用的引用。
所以A、B、C是错误的,D是正确的。
第三章:
类和对象
(一)
[3_1]答:
类声明的一般格式如下:
class类名
{public:
公有数据成员;
公有成员函数;
protected:
保护数据成员;
保护成员函数;
private:
私有数据成员;
私有成员函数;
};其中:
class是声明类的关键字;类名是要声明的类的名字;后面的花括号表示出类声明的范围;最后的分号表示类声明结束。
[3_2]答:
构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。
构造函数具有一些特殊的性质:
(1)构造函数的名字必须与类名相同
(2)构造函数可以有任意类型的参数,但不能指定返回类型。
它有隐含的返回值,该值在系统内部使用。
(3)构造函数是特殊的成员函数,函数体可写在类体内,也可写在类体外。
(4)构造函数可以重载,即一个类中可以定义多个参数个数或参数类型不同的构造函数。
(5)构造函数被声明为公有函数,但它不能象其它成员函数那样被显示地调用,它是在定义对象的同时被调用的。
析构函数也是一种特殊的成员函数。
它执行与构造函数相反的操作,通常用
于撤消对象时的一些清理任务,如释放分配给对象的内存空间等。
析构函数有以下一些特点:
(1)析构函数与构造函数名字相同,但它前面必须加一个波浪号(~)
(2)析构函数没有参数,不能指定返回类型,而且不能重载。
因此在一个类中只能有一个析构函数。
(3)当撤消对象时,编译系统会自动地调用析构函数。
[3_3]答:
B说明:
C++中对构造函数有一些规定:
不能带返回值;可以不带
参数;也可以缺省定义;但构造函数的名字与类名必须完全相同。
[3_4]答:
C说明:
C++中没有限定private、public、protected的书写次序。
但
是,不能在类的声明中给数据成员赋初值,数据成员的数据类型
也不能是register(寄存器类型),没有用private、public、protected
定义的数据成员是私有成员。
[3_5]答:
C说明:
C++中对析构函数也有一些规定:
没有参数;不能重载;析构函数的名字是在类名前加“~”;析构函数不能指定返回类型。
[3_6]答:
B说明:
构造函数的工作是在创建对象时执行的。
[3_27]答:
语句”p1.age=30;”出现错误。
因为age是私有数据成员,不能直接访问。
[3_28]答:
第1个错误:
printStu、setSno两个成员函数没有用public定义,则不
允许外部函数对对象进行操作。
第2个错误:
成员函数在类外定义,应加上类名“Student:
:
”。
第3个错误:
setAge应在类中说明,并且在类外定义时,应加上类名”Student:
:
”。
[3_29]答:
语句”Pointcpoint;”是错误的,它试图用私有的构造函数Point访问公有数据成员x和y,这是不对的。
[3_10]答:
语句Stackstt;”应该带参数,因为当类中没有定义构造函数时,编译器会自动生成一个缺省的不带参数的构造函数。
但是,如果类中有自己定义的构造函数后,编译器将不再自动生成一个缺省的构造函数。
例如:
将上述语句改成“Stackstt(10);”就正确了。
[3_30]:
下面是一个计数器的定义,请完成该类成员函数的实现
#include
classcounter
{public:
counter(intnumber);//构造函数
voidincrement();//给原值加1
voiddecrement();///给原值减1
intgetvalue();//取得计数器值
intprint();//显示计数
private:
intvalue;
};
counter:
:
counter(intnumber)//构造函数定义
{value=number;}
voidcounter:
:
increment()//给原值加1
{value++;}
voidcounter:
:
decrement()//给原值减1
{value--;}
intcounter:
:
getvalue()//取得计数器值
{returnvalue;}
intcounter:
:
print()//显示计数
{cout<<"valueis"<return0;
}
main()
{inti;
cin>>i;
countera(0);
for(intj=0;j
{a.increment();
a.getvalue();
a.print();
}
counterb(10);
for(intk=1;k
{b.decrement();
b.getvalue();
b.print();
}
return0;
}
习题:
[3_31]根据注释语句的提示,实现类Date的成员函数
#include
classDate
{public:
voidprintDate();//显示日期
voidsetDay(intd);//设置日期值
voidsetMonth(intm);//设置月的值
voidsetYear(inty);//设置年的值
private:
intday,month,year;
};
voidmain()
{DatetestDay;
testDay.setDay(5);
testDay.setMonth(10);
testDay.setYear(2003);
testDay.printDate();
}
voidDate:
:
printDate()
{cout<<"\nDateis"<cout<}
voidDate:
:
setDay(intd)
{day=d;}
voidDate:
:
setMon