第8章练习题
同步练习8.1
1.一个大的应用程序,通常由多个类构成,类与类之间互相协同工作, 它们之间有三种主要关系。下列不属于类之间关系的是( )。
(A)gets-a (B)has-a (C)uses-a (D)is-a
2.在C++中,类之间的继承关系具有( )。
(A)自反性 (B)对称性 (C)传递性 (D)反对称性
3.下列关于类之间关系的描述,正确的是( )。
(A)has-a表示一个类部分地使用另一个类 (B)uses-a表示类的包含关系
(C)is-a关系具有对称性。 (D)is-a机制称为“继承”
4.下列关于类的描述,正确的是( )。
(A)父类具有子类的特征 (B)一个类只能从一个类继承
(C)is-a关系具有传递性 (D)uses-a表示类的继承机制
5.下列关于类之间关系的描述,错误的是( )。
(A)用有向无环图(DAG)表示的类之间关系,称为“类格”
(B)DAG中每个结点是一个类定义,它的前驱结点称为基类
(C)DAG中每个结点是一个类定义,它的后继结点称为派生类
(D)DAG中每个结点是一个类定义,它有且仅有一个前驱结点
6.下列关于类的继承描述中,正确的是( )。
(A)派生类公有继承基类时,可以访问基类的所有数据成员,调用所有成员函数。
(B)派生类也是基类,所以它们是等价的。
(C)派生类对象不会建立基类的私有数据成员,所以不能访问基类的私有数据成员。
(D)一个基类可以有多个派生类,一个派生类可以有多个基类。
【解答】 A C D C D D
同步练习8.2
一、选择题
1.当一个派生类公有继承一个基类时,基类中的所有公有成员成为派生类的( )。
(A)public成员 (B)private成员 (C)protected成员 (D)友元
2.当一个派生类私有继承一个基类时,基类中的所有公有成员和保护成员成为派生类的( )。
(A)public成员 (B)private成员 (C)protected成员 (D)友元
3.当一个派生类保护继承一个基类时,基类中的所有公有成员和保护成员成为派生类的( )。
(A)public成员 (B)private成员 (C)protected成员 (D)友元
4.不论派生类以何种方式继承基类,都不能直接使用基类的( )。
(A)public成员 (B)private成员 (C)protected成员 (D)所有成员
5.在C++中,不加说明,则默认的继承方式是( )。
(A)public (B)private (C)protected (D)public或protected
6.某公有派生类的成员函数不能直接访问基类中继承来的某个成员,则该成员一定是基类中的( )。
(A)私有成员 (B)公有成员 (C)保护成员 (D)保护成员或私有成员
7.下列关于类层次中重名成员的描述,错误的是( )。
(A)C++允许派生类的成员与基类成员重名
(B)在派生类中访问重名成员时,屏蔽基类的同名成员
(C)在派生类中不能访问基类的同名成员
(D)如果要在派生类中访问基类的同名成员,可以显式地使用作用域符指定
8.下列关于类层次中静态成员的描述,正确的是( )。
(A)在基类中定义的静态成员,只能由基类的对象访问
(B)在基类中定义的静态成员,在整个类体系中共享
(C)在基类中定义的静态成员,不管派生类以何种方式继承,在类层次中具有相同的访问性质
(D)一旦在基类中定义了静态成员,就不能在派生类中再定义
【解答】 A B C B B A C B
二、程序练习
1.阅读程序,写出运行结果。
#include<iostream>
using namespace std;
class Base
{ public :
void get( int i,int j,int k,int l )
{ a = i; b = j; x = k; y = l; }
void print()
{ cout << "a = "<< a << '\t' << "b = " << b << '\t'
<< "x = " << x << '\t' << "y = " << y << endl;
}
int a, b;
protected :
int x, y;
};
class A : public Base
{ public :
void get( int i, int j, int k, int l )
{ Base obj3;
obj3.get( 50, 60, 70, 80 );
obj3.print();
a = i; b = j; x = k; y = l;
u = a + b + obj3.a; v = y - x + obj3.b;
}
void print()
{ cout << "a = " << a << '\t' << "b = " << b << '\t'
<< "x = " << x << '\t' << "y = " << y << endl;
cout << "u = " << u << '\t' << "v = " << v << endl;
}
private:
int u, v;
};
int main()
{ Base obj1;
A obj2;
obj1.get( 10, 20, 30, 40 );
obj2.get( 30, 40, 50, 60 );
obj1.print();
obj2.print();
}
【解答】
2.阅读程序,写出运行结果。
#include<iostream>
using namespace std;
class A
{ public :
A( int i, int j ) { a=i; b=j; }
void Add( int x, int y ) { a += x; b += y; }
void show() { cout << "("<<a<<")\t("<<b<<")\n"; }
private :
int a, b;
};
class B : public A
{ public :
B(int i, int j, int m, int n) : A( i, j ),x( m ), y( n ) {}
void show() { cout << "(" << x << ")\t(" << y << ")\n"; }
void fun() { Add( 3, 5 ); }
void ff() { A::show(); }
private :
int x, y;
};
int main()
{ A a( 1, 2 );
a.show();
B b( 3, 4, 5, 6 );
b.fun();
b.A::show();
b.show();
b.ff();
}
【解答】
3.编写程序,定义一个Rectangle类,它包含:
数据成员 length,width
成员函数 Rectangle( double l, double w ); //构造函数
double area(); //返回矩形面积
double getlength(); //返回数据成员length的值
double getwidth(); //返回数据成员width的值
再定义Rectangle的派生类Rectangular,它包含:
数据成员 height
成员函数 Rectangular( double l, double w, double h ); //构造函数
double volume(); //返回长方体体积
double getheight(); //返回数据成员height的值
在main函数中测试类体系,建立两个类的对象,显示它们的数据和面积、体积。
【解答】
#include <iostream>
using namespace std;
class rectangle
{
public:
rectangle( double l, double w )
{ length = l; width = w; }
double area()
{ return( length*width ); }
double getlength()
{ return length; }
double getwidth()
{ return width; }
private:
double length;
double width;
};
class rectangular: public rectangle
{
public:
rectangular( double l,double w,double h ) : rectangle( l,w )
{ height = h; }
double getheight()
{ return height; }
double volume()
{ return area() *height; }
private:
double height;
};
int main()
{
rectangle obj1( 2,8 );
rectangular obj2( 3,4,5 );
cout<<"length="<<obj1.getlength()<<'\t'<<"width="<<obj1.getwidth()<<endl;
cout<<"rectanglearea="<<obj1.area()<<endl;
cout<<"length="<<obj2.getlength()<<'\t'<<"width="<<obj2.getwidth();
cout<<'\t'<<"height="<<obj2.getheight()<<endl;
cout<<"rectangularvolume="<<obj2.volume()<<endl;
}
同步练习8.3
一、选择题
1.在C++中,可以被派生类继承的函数是( )。
(A)成员函数 (B)构造函数 (C)析构函数 (D)友元函数
2.下列关于派生类对象的初始化,叙述正确的是( )。
(A)是由派生类的构造函数实现的
(B)是由基类的构造函数实现的
(C)是由基类和派生类的构造函数实现的
(D)是系统自动完成的,不需要程序设计者干预
3.在创建派生类对象时,构造函数的执行顺序是( )。
(A)对象成员构造函数—基类构造函数—派生类本身的构造函数
(B)派生类本身的构造函数—基类构造函数—对象成员构造函数
(C)基类构造函数—派生类本身的构造函数—对象成员构造函数
(D)基类构造函数—对象成员构造函数—派生类本身的构造函数
4.在具有继承关系的类层次体系中,析构函数执行的顺序是( )。
(A)对象成员析构函数—基类析构函数—派生类本身的析构函数
(B)派生类本身的析构函数—对象成员析构函数—基类析构函数
(C)基类析构函数—派生类本身的析构函数—对象成员析构函数
(D)基类析构函数—对象成员析构函数—派生类本身的析构函数
5.在创建派生类对象时,类层次中构造函数的执行顺序是由( )。
(A)派生类的参数初始式列表的顺序决定的 (B)系统规定的
(C)是由类的书写顺序决定的 (D)是任意的
【解答】 A C D B B
二、程序练习
1.阅读程序,写出运行结果。
#include<iostream>
using namespace std;
class Base1
{ public :
Base1( int i )
{ cout << "调用基类Base1的构造函数:" << i << endl; }
};
class Base2
{ public:
Base2( int j )
{ cout << "调用基类Base2的构造函数:" << j << endl; }
};
class A : public Base1, public Base2
{ public :
A( int a,int b,int c,int d ):Base2(b),Base1(c),b2(a),b1(d)
{ cout << "调用派生类A的构造函数:" << a+b+c+d << endl; }
private :
Base1 b1;
Base2 b2;
};
int main()
{ A obj( 1, 2, 3, 4 );
}
【解答】
2.编写程序。已知有一个描述个人信息的Person类,数据成员记录个人姓名name和身份证号idNumber;成员函数print输出个人信息,构造函数完成对数据成员的初始化。请根据Person类和main函数运行结果,补充定义Person类的派生类Teacher类,除了记录教师的姓名和身份证号,还须记录职称title和工资wage;成员函数print输出教师个人信息,构造函数完成对数据成员的初始化。
#include <iostream>
#include<string>
using namespace std;
class Person
{ private:
string name; //姓名
string idNumber; //身份证号
public:
Person( const char *n, const char *i)
{ name = n;
idNumber = i;
}
void Print() const
{ cout<<"Name: "<<name<<"\n\tidNumber: "<<idNumber<<endl;
}
};
//此处定义Teacher类
int main()
{ Person p("张少华", "420106196611070538");
Teacher t("李若山", "420106195801247168", "教授", 5000);
p.Print();
t.Print();
}
程序运行结果:
Name:张少华
idNumber:420106196611070538
Name:李若山
idNumber:420106195801247168
Title:教授 Wage:5000
【解答】
#include <iostream>
#include<string>
using namespace std;
class Person
{
private:
string name; //姓名
string idNumber; //身份证号
public:
Person( const char *n, const char *i)
{
name = n;
idNumber = i;
}
void Print() const
{ cout<<"Name: "<<name<<"\n\tidNumber: "<<idNumber<<endl;
}
};
//定义Teacher类
class Teacher : public Person
{
private:
string title; //职称
double wage; //工资
public:
Teacher( const char *n, const char *i, const char *t, double w)
: Person(n, i)
{
title = t; wage = w;
}
void Print() const
{
Person::Print();
cout<<"\tTitle: "<<title<<"\tWage: "<<wage<<endl;
}
};
int main()
{
Person p("张少华", "420106196611070538");
Teacher t("李若山", "420106195801247168", "教授", 5000);
p.Print();
t.Print();
}
同步练习8.5
一、选择题
1.当不同的类具有相同的间接基类时,( )。
(A)各派生类无法按继承路线产生自己的基类版本
(B)为了建立唯一的间接基类版本,应该声明间接基类为虚基类
(C)为了建立唯一的间接基类版本,应该声明派生类虚继承基类
(D)一旦声明虚继承,基类的性质就改变了,不能再定义新的派生类
2.下列关于多继承的描述,错误的是( )。
(A)一个派生类对象可以拥有多个直接或间接基类的成员
(B)在多继承时不同的基类可以有同名成员
(C)对于不同基类的同名成员,派生类对象访问它们时不会出现二义性
(D)对于不同基类的不同名成员,派生类对象访问它们时不会出现二义性
3.下面关于基类和派生类的描述,正确的是( )。
(A)一个类可以被多次说明为一个派生类的直接基类,可以不止一次地成为间接基类
(B)一个类不能被多次说明为一个派生类的直接基类,可以不止一次地成为间接基类
(C)一个类不能被多次说明为一个派生类的直接基类,且只能成为一次间接基类
(D)一个类可以被多次说明为一个派生类的直接基类,但只能成为一次间接基类
4.下列关于虚继承的说明形式的描述,正确的是( )。
(A)在派生类类名前添加关键字virtual (B)在基类类名前添加关键字virtual
(C)在基类类名后添加关键字virtual
(D)在派生类类名后,类继承的关键字之前添加关键字virtual
5.设置虚基类的目的是( )。
(A)简化程序 (B)消除二义性 (C)提高运行效率 (D)减少目标代码
【解答】 C C B D B
二、程序练习
1.阅读程序,写出运行结果。
#include<iostream>
using namespace std;
class A
{ public :
A(const char *s) { cout << s << endl; }
~A() {}
};
class B : virtual public A
{ public :
B(const char *s1, const char *s2) : A( s1 ) { cout << s2 << endl; }
};
class C : virtual public A
{ public :
C(const char *s1, const char *s2):A(s1) { cout << s2 << endl; }
};
class D : public B, public C
{ public :
D( const char *s1,const char *s2,const char *s3,const char *s4 ):
B( s1, s2 ), C( s1, s3 ), A( s1 )
{ cout << s4 << endl; }
};
int main()
{ D *ptr = new D( "class A", "class B", "class C", "class D" );
delete ptr;
}
【解答】
综合练习
一、思考题
1.函数和类这两种程序模块都可以实现软件重用,它们之间有什么区别?
【解答】
函数是基于参数集的功能抽象模块,以调用方式实现软件重用,函数之间没有逻辑关系。
类是数据属性与操作的封装,以继承方式实现软件重用,类之间构成有向无回图的类格。
2.按照类成员的访问特性、类层次的继承特点,制作一张表格,总结各种类成员在基类、派生类中的可见性和作用域。
【解答】
基类成员 派生类继承 | public | protected | private |
public | 在派生类中访问特性不变。派生类和类外均可见,有作用域。 | 在派生类中访问特性不变。类体系中可见。 | 基类私有成员,仅在基类中可见。 |
protected | 成为派生类保护段成员。在整个类体系中可见。 | ||
private | 成为派生类私有成员。仅在派生类和基类中可见。 | ||
派生类不论以何种方式继承基类,基类所有成员在整个类体系有作用域。 |
3.若有以下说明语句:
class A
{ private : int a1;
public : int a2; double x;
/*…*/
};
class B : private A
{ private : int b1;
public : int b2; double x;
/*…*/
};
B b;
对象b将会生成什么数据成员?与继承关系、访问特性、名字有关吗?
【解答】
对象b生成的数据成员有a1 a2 A::x b1 b2 B::x,共六个数据成员。数据成员的建立和继承关系、访问特性、名字无关。
4.若有以下说明语句:
class A
{ /*…*/
public : void sameFun();
};
class B : public A
{ /*…*/
public : void sameFun();
};
void comFun()
{ A a;
B b;
/*…*/
}
(1)若在B::sameFun中调用A::sameFun,语句格式如何?它将在什么对象上操作?
(2)在comFun中可以用什么方式调用A::sameFun和B::sameFun?语句格式如何?它们将可以在什么对象上操作?
【解答】
(1)若要在B::sameFun中调用A::sameFun,语句形式应为:
A::samefun(); //域作用符说明调用基类函数
调用的A::samefun将在B类对象上进行操作。
(2)在comFun中调用B::sameFun和A::sameFun的方式有:
a.A::sameFun(); //通过A类对象调用A::sameFun,在a类对象上操作
b.sameFun(); //通过B类对象调用B::sameFun,在b类对象上操作
b.A::sameFun(); //通过B类对象调用A::sameFun
//在b类对象上(对由基类继承下来的数据成员)操作
5.有人定义一个教师类派生一个学生类。他认为“姓名”和“性别”是教师、学生共有的属性,声明为public,“职称”和“工资”是教师特有的,声明为private。在学生类中定义特有的属性“班级”和“成绩”。所以有:
class teacher
{ public:
char name[20]; char sex;
//…
private:
char title[20]; double salary;
};
class student : public teacher
{ //…
private:
char grade[20]; int score;
};
你认为这样定义合适吗?请给出你认为合理的类结构定义。
【解答】
不合适,这样导致数据冗余。合理的结构是提取它们共有的数据和操作定义一个基类,然后分别定义teacher和student作为派生类。
class person
{ protected:
char name[20]; char sex;
//……
};
class teacher : public teache
{ //……
private:
char title[20]; double salary;
};
class student : public teacher
{ //……
private :
char grade[20] ; int score;
};
6.在第6章的例6-21中,定义Student类包含了Date类成员。可以用继承方式把Student类定义为Date类的派生类吗?如何改写程序?请你试一试。
【解答】
可以用继承方式改写。程序略。
7.“虚基类”是通过什么方式定义的?如果类A有派生类B、C,类A是类B虚基类,那么它也一定是类C的虚基类吗?为什么?
【解答】
虚基类是在声明派生类时,指定继承方式时声明的,声明虚基类的一般形式为:
class 派生类名 : virtual 继承方式 基类名
若类A是类B和类C的虚基类,但不一定是类D的虚基类,原因在于“虚基类”中的“虚”不是基类本身的性质。而是派生类在继承过程中的特性。关键字virtual只是说明该派生类把基类当作虚基类继承,不能说明基类其他派生类继承基类的方式
8.在具有虚继承的类体系中,建立派生类对象时,以什么顺序调用构造函数?请用简单程序验证你的分析。
【解答】
在具有虚继承的类体系中,建立派生类对象时先调用间接基类构造函数,再按照派生类定义时各个直接基类继承的顺序调用直接基类的构造函数,最后再对派生类对象自身构造函数。
另外,C++为了保证虚基类构造函数只被建立对象的类执行一次,规定在创建对象的派生类构造函数中只调用虚基类的构造函数和进行(执行)自身的初始化。参数表中的其他调用被忽略,即直接基类的构造函数只调用系统自带的版本,或调用自定义版本但不对虚基类数据成员初始化。
程序略。
二、程序设计
1.假设某销售公司有一般员工、销售员工和销售经理。月工资的计算办法是:
一般员工月薪=基本工资;
销售员工月薪=基本工资+销售额*提成率;
销售经理月薪=基本工资+职务工资+销售额*提成率。
编写程序,定义一个表示一般员工的基类Employee,它包含三个表示员工基本信息的数据成员:编号number、姓名name和基本工资basicSalary。
由Employee类派生销售员工Salesman类,Salesman类包含两个新数据成员:销售额sales和静态数据成员提成比例commrate。
再由Salesman类派生表示销售经理的Salesmanager类。Salesmanager类包含新数据成员:岗位工资jobSalary。
为这些类定义初始化数据的构造函数,以及输入数据input、计算工资pay和输出工资条print的成员函数。
设公司员工的基本工资是2000元,销售经理的岗位工资是3000元,提成率=5/1000。在main函数中,输入若干个不同类型的员工信息测试你的类结构。
【解答】
#include <iostream>
using namespace std;
class Employee
{
public:
Employee( char Snumber[]="\0", char Sname[]="\0", double bSalary=2000 )
{
strcpy_s(number,Snumber);
strcpy_s(name,Sname);
basicSalary=bSalary;
}
void input()
{
cout << "编号:" ; cin >> number;
cout <<"姓名:" ; cin >> name;
}
void print()
{
cout<<"员工 :"<<name<<"\t\t编号:"<<number<<"\t\t本月工资:"<<basicSalary<<endl;
}
protected:
char number[5];
char name[10];
double basicSalary;
};
class Salesman: public Employee
{
public:
Salesman(int sal=0)
{ sales=sal; }
void input()
{
Employee::input();
cout<<"本月个人销售额:";
cin>>sales;
}
void pay()
{
salary = basicSalary+sales*commrate;
}
void print()
{
pay();
cout<<"销售员 :"<<name<<"\t\t编号:"<<number<<"\t\t本月工资:"<<salary<<endl; }
protected:
static double commrate;
int sales;
double salary;
};
double Salesman :: commrate=0.005;
class Salesmanager : public Salesman
{
public:
Salesmanager(double jSalary=3000)
{
jobSalary = jSalary;
}
void input()
{
Employee::input();
cout<<"本月部门销售额:";
cin>>sales;
}
void pay()
{
salary = jobSalary + sales*commrate;
}
void print()
{
pay();
cout<<"销售经理 :"<<name<<"\t\t编号:"<<number<<"\t\t本月工资:"<<salary<<endl; }
private:
double jobSalary;
};
int main()
{
cout<<"基本员工\n";
Employee emp1;
emp1.input();
emp1.print();
cout<<"销售员\n";
Salesman emp2;
emp2.input();
emp2.print();
cout<<"销售经理\n";
Salesmanager emp3;
emp3.input();
emp3.print();
}
2.试写出你所能想到的所有形状(包括二维的和三维的),生成一个形状层次类体系。生成的类体系以Shape作为基类,并由此派生出TwoDimShape类和ThreeDimShape类。它们的派生类是不同的形状类。定义类体系中的每个类,并用main函数进行测试。
【解答】
略。
3.为第7章综合练习的程序设计第1题和第2题中的Integer类和Real类定义一个派生类IntReal:
class IntReal : public Integer, public Real;
使其可以进行+、-、*、/、= 的左、右操作数类型不同的相容运算,并符合原有运算类型转换的语义规则。
【解答】
略。
4.使用Integer类,定义派生类Vector类:
class Integer
{ //…
protected :
int n;
};
class Vector:public Integer
{ //…
protected :
int *v;
};
其中,数据成员v用于建立向量,n为向量长度。要求:类的成员函数可以实现向量的基本算术运算。
【解答】
略。
5.用包含方式改写第4题中的Vector类,使其实现相同的功能。
class Vector
{ //…
protected :
Integer *v;
Integersize;
};
【解答】
略。
6.使用第5题定义的Vector类,定义它的派生类Matrix,实现矩阵的基本算术运算。
【解答】
略。
7.用包含方式改写第6题的Matrix类,使其实现相同的功能。
【解答】
略。
8.设计快捷店会员的简单管理程序。基本要求如下:
(1)定义人民币RMB类,实现人民币的基本运算和显示。
(2)定义会员member类,表示会员的基本信息,包括:编号(按建立会员的顺序自动生成),姓名,密码,电话。提供输入、输出信息等功能。
(3)由RMB类和member类共同派生一个会员卡memberCar类,提供新建会员、充值、消费和查询余额等功能。
(4)main函数定义一个memberCar类数组或链表,保存会员卡,模拟一个快捷店的会员卡管理功能,主要包括:
① 新建会员;
② 已有会员充值;
③ 已有会员消费(凭密码,不能透支);
④ 输出快捷店当前会员数,营业额(收、支情况)。
【解答】
略。