Visual_C++面向对象程序设计教程与实验(第二版)清华大学出版社1—8章答案
发布时间:2024-10-23
发布时间:2024-10-23
第一章
1. 什么是面向对象程序设计?它与传统的结构化程序设计有什么不同?
面向对象程序设计既吸取了结构化程序设计的一切优点,又考虑了现实世界与面向对象解空间的映射关系,它所追求的目标是将现实世界的问题求解尽可能简单化。
面向对象程序设计将数据及对数据的操作放在一起,作为一个相互依存、不可分割的整体来处理,它采用了数据抽象和信息隐藏技术。它将对象及对对象的操作抽象成一种新的数据类型—类,并且考虑不同对象之间的联系和对象所在类的重要性。
面向对象程序设计优于传统的结构化程序设计,其优越性表现在,它有希望解决软件工程的两个主要的问题——软件复杂性控制和软件生产率的提高,此外它还符合人类的思维习惯,能够自然地表现现实世界的实体和问题,它对软件开发过程具有重要的意义。
在面向对象程序设计中可以用下面的式子表示程序:
程序=对象+对象+ +对象
对象=算法+数据结构+程序设计语言+语言环境
在结构化程序设计中可以用下面的式子表示程序:
程序=数据结构+算法+程序设计语言+语言环境
2. 面向对象程序设计语言有哪几类?
(1) LISP家族
LISP是50年代开发出来的一种语言,它以表处理为特色,是一种人工智能语言,70年代以来,在LISP基础上开发了很多LISP家族的面向对象语言。
(2) Simula
Simula语言是60年代开发出来的,在Simula中引入了几个面向对象程序设计语言中最重要的概念和特性,即数据抽象、类和继承性机制。Simula67是它具有代表性的一个版本,70年代发展起来的CLU,Ada,Modula-2等语言是在
它的基础上发展起来的。
(3) Smalltalk
Smalltalk是第一个真正的面向对象程序设计语言,它体现了纯粹的OOP设计思想,是最纯的OOP语言。它起源于Simula语言。尽管Smalltalk不断完善,但在那个时期,面向对象程序设计语言并没有得到广泛的重视,程序设计的主流是结构化程序设计。
(4) C家族
在19世纪80年代,C语言成为一种极其流行、应用非常广泛的语言。C++是在C语言的基础上进行扩充,并增加了类似Smalltalk语言中相应的对象机制。它将“类”看作是用户定义类型,使其扩充比较自然。C++以其高效的执行效率赢得了广大程序设计员的青睐,在C++中提供了对C语言的兼容性,因此,很多已有的C程序稍加改造甚至不加改造就可以重用,许多有效的算法也可以重新利用。它是一种混合型的面向对象程序设计语言,由于它的出现,才使面向对象的程序设计语言越来越得到重视和广泛的应用。
JAVA语言是一种适用于分布式计算的新型面向对象程序设计语言,可以看作是C++语言的派生,它从C++语言中继承了大量的语言成分,抛弃了C++语言中冗余的、容易引起问题的功能,增加了多线程、异常处理、网络程序设计等方面的支持,掌握了C++语言,可以很快学会JAVA 语言。
3. 面向对象系统有哪些特性?分别加以解释。
(1) 抽象性(abstract)
抽象是一种从一般的观点看待事物的方法,它要求程序员集中于事物的本质特征,而不是具体细节或具体实现。类的概念来自人们认识自然、认识社会的过程。在这一过程中,人们主要使用两种方法:从特殊到一般的归纳法和从一般到特殊的演绎法。在归纳的过程中,我们从一个个具体的事物中把共同的特征抽取出来,形成一个一般的概念,这就是“归类”;在演绎的过程中,我们又把同类的事物,根据不同的特征分成不同的小类,这就是“分类”。对于一个具体的类,它有许多具体的个体,我们称这些个体叫做“对象”。
(2) 封装性(encapsulation)
所谓数据封装就是指一组数据和与这组数据有关的操作集合组装在一起,形成一个能动的实体,也就是对象。数据封装就是给数据提供了与外界联系的标准接口,无论是谁,只有通过这些接口,使用规范的方式,才能访问这些数据。数据封装是软件工程发展的必然产物,使得程序员在设计程序时可以专注于自己的对象,同时也切断了不同模块之间数据的非法使用,减少了出错的可能性。
(3) 继承性(inheritance)
从已有的对象类型出发建立一种新的对象类型,使它继承原对象的特点和功能,这种思想是面向对象设计方法的主要贡献。继承是对许多问题中分层特性的一种自然描述,因而也是类的具体化和被重新利用的一种手段,它所表达的就是一种对象类之间的相交关系。它使得某类对象可以继承另外一类对象的特征和能力。继承所具有的作用有两个方面:一方面可以减少代码冗余;另一方面可以通过协调性来减少相互之间的接口和界面。
(4) 多态性(polymorphism)
不同的对象接收到相同的消息时产生多种完全不同的行为的现象称为多态性。C++语言支持两种多态性即编译时的多态性和运行时的多态性。编译时的多态性通过重载函数实现,而运行时的多态性通过虚函数实现。使用多态性可以大大提高了我们解决复杂问题的能力。
4. 解释类、对象、消息和方法的概念。
(1) 类(class)
在面向对象系统中,并不是将各个具体的对象都进行描述,而是忽略其非本质的特性,找出其共性,将对象划分成不同的类,这一过程为抽象过程。类是对象的抽象及描述,是具有共同属性和操作的多个对象的相似特性的统一描述体。在类的描述中,每个类要有一个名字标识,用以表示一组对象的共同特征。类中的每个对象都是该类的实例。类提供了完整的解决特定问题的能力,因为类描述了数据结构(对象属性)、算法(服务、方法)和外部接口(消息协议),是一种用户自定义的数据类型。
(2) 对象(object)
在面向对象系统中,对象是系统中用来描述客观事物的一个实体,它是构成系统的一个
基本单位。一个对象由一组属性和对这组属性进行操作的一组服务构成。属性和服务是构成对象的两个主要因素,属性是一组数据机构的集合,表示对象的一种状态,对象的状态只供对象自身使用,用来描述静态特性,而服务是用来描述对象动态特征(行为)的一个操作序列,是对象一组功能的体现。
对象是类的实例。
(3) 消息(message)
消息是面向对象系统中实现对象间的通信和请求任务的操作,是要求某个对象执行其中某个功能操作的规格说明。发送消息的对象称为发送者,接受消息的对象称为接收者。对象间的联系,只能通过消息来进行。对象在接收到消息时才被激活。
(4) 方法(method)
在面向对象程序设计中,要求某一对象做某一操作时,就向对象发送一个相应的消息,当对象接收到发向它的消息时,就调用有关的方法,执行相应的操作。方法就是对象所能执行的操作。方法包括界面和方法体两部分。方法的界面就是消息的模式,它给出了方法的调用协议;方法体则是实现某种操作的一系列计算步骤,也就是一段程序。消息和方法的关系是:对象根据接收到的消息,调用相应的方法;反过来,有了方法,对象才能响应相应的消息。所以消息模式与方法界面应该是一致的。同时,只要方法界面保持不变,方法体的改动不会影响方法的调用。在C++语言中方法是通过函数来实现的,称为成员函数。
第二章
1.分析下列程序的执行结果:
输出随机数
2. 分析下列程序的执行结果:
i=0
3. C++语言对C语言在结构化程序设计方面进行了哪些扩充? 主要在以下方面进行了扩充:
文件扩展名、注释符、名字空间、输入输出、变量的定义、强制类型转换、动态内存的分配与释放、作用域运算符::、引用、const修饰符、字符串、函数
4. 下述C++程序有若干处错误,试找出并纠正之。
正确程序为:
#include<iostream.h>
const float PAI=3.14159265;
float square(float r)
{return PAI*r*r;}
float square(float high,float length=0 )
{return high*length;}
float (*fs)(float,float=0);
void main()
{
fs=□
cout<<"The circle's square is "<<fs(1.0)<<'\n'; }
5. 引用类型与指针类型有什么区别?
指针的内容或值是某一变量的内存单元地址,而引用则与初始化它的变量具有相同的内存单元地址。指针是个变量,可以把它再赋值成其它的地址,然而,建立引用时必须进行初始化并且决不会再指向其它不同的变量。
C++没有提供访问引用本身地址的方法,因为它与指针或其它变量的地址不同,它没有任何意义。引用总是作为变量的别名使用,引用的地址也就是变量的地址。引用一旦初始化,就不会与初始化它的变量分开。
6.函数、内联函数以及宏的区别。
程序的模块在C++中通过函数来实现,函数由函数说明和函数体2部分组成。 内联函数是C++语言特有的一种函数附加类别,是通过在函数声明之前插入“inline”关键字实现的。编译器会将编译后的全部内联函数的目的机器代码复制到程序内所有的引用位置并把往返传送的数据也都溶合进引用位置的计算当中,用来避免函数调用机制所带来的开销,提高程序的执行效率。显然这是以增加程序代码空间为代价换来的。
宏定义是编译预处理命令,分为带参数的和不带参数的宏定义。在编译之前进行预处理时,用宏定义中的字符串替换程序中所有出现的宏名。
7. 函数重载有什么好处?
函数重载使函数方便使用,便于记忆,也使程序设计更加灵活,增加程序的可读性。例如,求两个数中最大值的函数max,不管其参数是整数类型、实数类型、字符串,都可以使用同名函数来实现,调用时只需使用max就可以了,编译器将根据实参的类型判断应该调用哪一个函数。
8. 模板有什么作用?函数模板和模板函数有什么区别?
所谓模板是一种使用无类型参数来产生一系列函数或类的机制,是C++的一个重要特征。通过模板可以产生类或函数的集合,使它们操作不同的数据类型,从而避免为每一种数据类型产生一个单独的类或函数。
函数模板提供了传递类型的机制。函数模板定义不是一个实实在在的函数,编译系统不为其产生任何执行代码。该定义只是对函数的描述,表示它每次能单
独处理在类型形式参数表中说明的数据类型。
函数模板只是说明,不能直接执行,需要实例化为模板函数后才能执行。当编译系统发现有一个函数调用:
<函数名><实参表>;
时,将根据<实参表>中的类型生成一个重载函数,即模板函数。该模板函数的定义体与函数模板的函数定义体相同,而<形参表>的类型则以<实参表>的实际类型为依据。
第三章
1. 为什么要引入构造函数和析构函数?
对象的初始化是指对象数据成员的初始化,在使用对象前,一定要初始化。由于数据成员一般为私有的(private),所以不能直接赋值。对对象初始化有以下两种方法:
类中提供一个普通成员函数来初始化,但是会造成使用上的不便(使用对象前必须显式调用该函数)和不安全(未调用初始化函数就使用对象)。
当定义对象时,编译程序自动调用构造函数。
析构函数的功能是当对象被撤消时,释放该对象占用的内存空间。析构函数的作用与构造函数正好相反,一般情况下,析构函数执行构造函数的逆操作。在对象消亡时,系统将自动调用析构函数,执行一些在对象撤消前必须执行的清理任务。
2. 类的公有、私有和保护成员之间的区别是什么?
① 私有成员private: 私有成员是在类中被隐藏的部分,它往往是用来描述该类对象属性的一些数据成员,私有成员只能由本类的成员函数或某些特殊说明的函数(如第4章讲到的友员函数)访问,而类的外部根本就无法访问,实现了访问权限的有效控制,使数据得到有效的保护,有利于数据的隐藏,使内部数据不能被任意的访问和修改,也不会对该类以外的其余部分造成影响,使模块之间的相互作用被降低到最小。private成员若处于类声明中的第一部分,可省略关键字private。
② 公有成员public:公有成员对外是完全开放的,公有成员一般是成员函
数,它提供了外部程序与类的接口功能,用户通过公有成员访问该类对象中的数据。
③ 保护成员protected: 只能由该类的成员函数,友元,公有派生类成员函数访问的成员。保护成员与私有成员在一般情况下含义相同,它们的区别体现在类的继承中对产生的新类的影响不同,具体内容将在第5章中介绍。缺省访问控制(未指定private、protected、public访问权限)时,系统认为是私有private成员。
3. 什么是拷贝构造函数,它何时被调用?
拷贝构造函数的功能是用一个已有的对象来初始化一个被创建的同类对象,是一种特殊的构造函数,具有一般构造函数的所有特性,当创建一个新对象时系统自动调用它;其形参是本类对象的引用,它的特殊功能是将参数代表的对象逐域拷贝到新创建的对象中。
在以下四种情况下系统会自动调用拷贝构造函数:
① 用类的一个对象去初始化另一个对象
cat cat1;
cat cat2(cat1); // 创建 cat2时系统自动调用拷贝构造函数,
// 用cat1初始化cat2。
② 用类的一个对象去初始化另一个对象时的另外一种形式
Cat cat2=cat1; // 注意并非cat cat1,cat2; cat2=cat1;
③ 对象作为函数参数传递时,调用拷贝构造函数。
f(cat a){ } // 定义f函数,形参为cat类对象
cat b; // 定义对象b f(b); // 进行f函数调用时,系统自动调用拷贝构造函数 ④ 如果函数的返回值是类的对象,函数调用返回时,调用拷贝构造函数。
4. 设计一个计数器类,当建立该类的对象时其初始状态为0,考虑为计数器定义哪些成员?
// counter.h
#ifndef counter_h
#define counter_h
class counter{
private:
int count;
public:
counter();
void setCount(int i);
int getCount();
void displayCount();
void incrementCount();
void decrementCount();
~counter(){}
};
#endif
// counter.cpp
#include "counter.h"
#include<iostream.h>
counter::counter(){
count = 0;
}
void counter::displayCount(){
cout << count << endl;
}
int counter::getCount(){
return count;
}
void counter::setCount(int i){
count = i;
}
void counter::incrementCount(){
count++;
}
void counter::decrementCount(){
count--;
}
// MAIN.CPP
#include "counter.h"
#include<iostream.h>
void main(){
counter c1;
c1.displayCount();
c1.setCount(4);
c1.displayCount();
for(int i=0; i<=10;i++){
c1.incrementCount();
c1.displayCount();
}
}
5. 定义一个时间类,能提供和设置由时、分、秒组成的时间,并编写出应用程序,定义时间对象,设置时间,输出该对象提供的时间。
#include <iostream.h>
class Time{
int hour,minute,second;
public:
Time(int h=0,int m=0, int s=0){
hour = h;
minute = m;
second = s;
}
void setHour(int h){
hour = h;
}
void setMinute(int m){
minute = m;
}
void setSecond(int s){
second = s;
}
void display(){
cout << hour << ": " << minute << ": " << second <<endl;
}
};
void main(){
Time t;
t.display();
t.setHour(13);
t.setMinute(15);
t.setSecond(30);
t.display();
}
6.设计一个学生类student,它具有的私有数据成员是:注册号、姓名、数学、英语、计算机成绩;具有的公有成员函数是:求三门课总成绩的函数sum;求三门课平均成绩的函数average;显示学生数据信息的函数print;获取学生注册号的函数get_reg_num;设置学生数据信息的函数set_stu_inf。
编制主函数,说明一个student类对象的数组并进行全班学生信息的输入与设置,而后求出每一学生的总成绩、平均成绩、全班学生总成绩最高分、全班学生总平均分,并在输入一个注册号后,输出该学生有关的全部数据信息。
#include<iostream>
#include<string>
using namespace std;
class Student{
private:
int num;
char name[10];
float math;
float english;
float computer;
public:
void set_stu_inf(int n,char *ch,float m,float e,float c)
{
num=n; strcpy(name,ch); math=m; english=e; computer=c;
}
float sum()
{
return (math+english+computer);
}
float average()
{
return (math+english+computer)/3;
}
int get_reg_num()
{
return num;
}
void print()
{
cout<<"学号:"<<num<<endl
<<"姓名:"<<name<<endl
<<"数学:"<<math<<endl
<<"英语:"<<english<<endl
<<"计算机:"<<computer<<endl
<<"总分:"<<sum()<<endl
<<"平均分:"<<average()<<endl;
}
};
void main()
{
Student stu[50];
int i,q,a,z,x,max=0,aver=0; //i为循环变量,q:学号;a:数学
//成绩; z:英语成绩; x:计算机成绩
int count = 0; //表示学生人数
char* we=new char[10];
// 输入学生信息
for(;;)
{
cout<<"请输入学生的学号、姓名、数学成绩、英语成绩、计算机成绩:(若输入的学号为0则表示退出)" << endl;
cin>>q>>we>>a>>z>>x;
if (q ==0 )
break;
stu[count++].set_stu_inf(q,we,a,z,x);
if(max>a+z+x);
else max=a+z+x;
aver+=(a+z+x);
}
// 输出所有学生信息
cout<<"学生信息为:"<<endl<<endl;
for( i = 0; i < count; i++){
stu[i].print();
cout<<endl;
}
cout<<"全班学生总成绩最高分为"<<max<<endl
<<"全班学生总平均分为"<<aver/3<<endl<<endl;
cout<<"请输入要查的学生的学号:"<<endl;
cin>>q;
for( i = 0; i < count; i++){
if (q==stu[i].get_reg_num())
{
cout<<"此学生信息为:"<<endl;
stu[i].print();
break;
}
}
if (i==count)
cout<<"查无此人"<<endl;
}
7. 模拟栈模型的操作,考虑顺序栈和链栈两种形式。
链栈:
#include <iostream.h>
class Stack //定义堆栈类
{
struct Node
{
int content;
Node *next;
} *top;
public:
Stack() { top = NULL; } // 构造函数的定义
bool push(int i); // 压栈成员函数的声明
bool pop(int& i); // 弹栈成员函数的声明
};
bool Stack::push(int i) // 压栈成员函数的定义
{
Node *p=new Node;
if (p == NULL)
{
cout << "Stack is overflow.\n";
return false;
}
else
{
p->content = i;
p->next = top;
top = p;
return true;
}
}
bool Stack::pop(int& i) // 弹栈成员函数的定义
{
if (top == NULL)
{
cout << "Stack is empty.\n";
return false;
}
else
{
Node *p=top;
top = top->next;
i = p->content;
delete p;
return true;
}
}
void main()
{
Stack st1,st2; // 定义对象st1和st2
int x;
for(int i=1;i<=5;i++)
{
st1.push(i); // 压栈成员函数的调用
st2.push(i); // 压栈成员函数的调用
}
cout<<"stack1:"<<endl;
for(i=1;i<=3;i++)
{
st1.pop(x); // 弹栈成员函数的调用
cout<<x<<endl;
}
st1.push(20);
for(i=1;i<=4;i++)
{
if(st1.pop(x))
cout<<x<<endl;
else
break;
}
cout<<"stack2:"<<endl;
while(st2.pop(x))
cout<<x<<endl;
}
顺序栈采用一维数组来实现。(略)
8. 写出下列程序的运行结果:
Constructing 22 11
Constructing 20 10
display:22 11
display:20 10
Destructing20 10
Destructing22 11
第五章
1. 什么是类的继承与派生?
继承性是面向对象程序设计的第二个重要特性,通过继承实现了数据抽象基础上的代码重用。继承是对许多问题中分层特性的一种自然描述,因而也是类的具体化和被重新利用的一种手段,它所表达的就是一种对象类之间的相交关系。它使得某类对象可以继承另外一类对象的特征和能力。继承所具有的作用有两个方面:一方面可以减少代码冗余;另一方面可以通过协调性来减少相互之间的接口和界面。通过继承方式定义的子类也称为派生类。
2. 类的三种继承方式之间的区别是什么?
类的继承方式有public(公有)继承、protected(保护)继承和private(私有)继承三种。对于不同的继承方式,会导致基类成员原来的访问属性在派生类中有所变化。表
5.1列出了不同继承方式下基类成员访问属性的变化情况。
表5.1 不同继承方式下基类成员的访问属性
说明:
该表第1列给出3种继承方式,第1行给出基类成员的3种访问属性。其余单元格内容为基类成员在派生类中的访问属性。
从表中可以看出:
(1) 基类的私有成员在派生类中均是不可访问的,它只能由基类的成员访问。
(2) 在公有继承方式下,基类中的公有成员和保护成员在派生类中的访问属性不变。
(3) 在保护继承方式下,基类中的公有成员和保护成员在派生类中均为保护的。
(4) 在私有继承方式下,基类中的公有成员和保护成员在派生类中均为私有的。 需要注意的是:
保护成员与私有成员唯一的不同是当发生派生后,处在基类protected区的成员可被派生类直接访问,而私有成员在派生类中是不可访问的。在同一类中私有成员和保护成员的用法完全一样。
3. 派生类能否直接访问基类的私有成员?若否,应如何实现?
派生类不能直接访问基类的私有成员。
具体实现方式:(1) 在类定义体中增加保护段
为了便于派生类的访问,可以将基类私有成员中需提供给派生类访问的部分定义为保护段成员。保护段成员可以被它的派生类访问,但是对于外界是隐藏起来的。这样,既方便了派生类的访问,又禁止外界对它的派生类访问。
这种方式的缺点是在公有派生的情况下,如果把成员设为保护访问控制,则为外界访问基类的保护段成员提供了机会,而三种派生方式,我们经常使用的是公有派生。
(2) 将需访问基类私有成员的派生类成员函数声明为基类的友元
这样派生类中的其它成员函数均无权访问它,外界不可能通过派生新类来达到访问基类私有成员的目的。