C++的一个主要目标是促进代码重用。公有继承是实现这种目标的机制之一,但并不是唯一的机制。
本章将介绍其他方法,其中之一是使用这样的类成员:本身是另一个类的对象。这种方法称为包含(containment)、组合(composition)或层次化(layering)。另一种方法是使用私有或保护继承。通常,包含、私有继承和保护继承用于实现has-a关系,即新的类将包含另一个类的对象。
类模板使得程序员能够使用通用术语定义类,然后使用模板来创建针对特定类型定义的特殊类。
学生是什么?是有名字和有一系列考试分数的人。
valarray类是由头文件 valarray支持的。顾名思义,这个类用于处理数值(或具有类似特性的类),它支持诸如将数组中所有元素的值相加以及在数组中找出最大和最小的值等操作。valarray被定义为一个模板类,以便能够处理不同的数据类型。
valarray<int> q_values; // an array of int
valarray<double> weights; // an array of double
double gpa[5] = {3.1, 3.5, 3.8, 2.9, 3.3};
valarray<double> v1; // an array of double, size 0
valarray<int> v2(8); // an array of 8 int elements
valarray<int> v3(10,8); // an array of 8 int elements, each set to 10
valarray<double> v4(gpa, 4); // an array of 4 elements initialized to the first 4 elements of gpa
//In C++11,using an initializer list is also allowed.
valarray<int> v5 = {20, 32, 17, 9}; // C++11
您可能想以公有的方式从这两个类派生出Student类,这将是多重公有继承,C++允许这样做,但在这里并不合适,因为学生与这些类之间的关系不是is-a模型。学生不是姓名,也不是一组考试成绩。这里的关系是has-a,学生有姓名,也有一组考试分数。通常,用于建立has-a关系的C++技术是组合(包含),即创建一个包含其他类对象的类。
class Student
{
private:
string name; // use a string object for name
valarray<double> scores; // use a valarray object for scores
...
};
同样,上述类将数据成员声明为私有的。这意味着Student类的成员函数可以使用string和valarray类的公有接口来访问和修改name和scores对象,但在类的外面不能这样做,而只能通过Student类的公有接口访问name和score(请参见图14)。对于这种情况,通常被描述为Student类获得了其成员对象的实现,但没有继承接口。
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
对于has-a关系来说,类对象不能自动获得被包含对象的接口是一件好事。在学生类中,姓名字符串的+操作符重载是不需要的,另一方面,被包含的类的接口部分对新类来说可能是有意义的。
当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。例如,假设Student构造函数如下:
Student(const char * str, const double * pd, int n)
: scores(pd, n), name(str) {}
则name成员仍将首先被初始化,因为在类定义中它首先被声明。对于这个例子来说,初始化顺序并不重要,但如果代码使用一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常重要了。
studentc.h
#pragma once
// studentc.h -- defining a Student class using containment
#ifndef STUDENTC_H_
#define STUDENTC_H_
#include
#include
#include
class Student
{
private:
typedef std::valarray<double> ArrayDb;
std::string name; // contained object
ArrayDb scores; // contained object
// private method for scores output
std::ostream& arr_out(std::ostream& os) const;
public:
Student() : name("Null Student"), scores() {}
explicit Student(const std::string& s)
: name(s), scores() {}
explicit Student(int n) : name("Nully"), scores(n) {}
Student(const std::string& s, int n)
: name(s), scores(n) {}
Student(const std::string& s, const ArrayDb& a)
: name(s), scores(a) {}
Student(const char* str, const double* pd, int n)
: name(str), scores(pd, n) {}
~Student() {}
double Average() const;
const std::string& Name() const;
double& operator[](int i);
double operator[](int i) const;
// friends
// input
friend std::istream& operator>>(std::istream& is,
Student& stu); // 1 word
friend std::istream& getline(std::istream& is,
Student& stu); // 1 line
// output
friend std::ostream& operator<<(std::ostream& os,
const Student& stu);
};
#endif
studentc.cpp
// studentc.cpp -- Student class using containment
#include "studentc.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
//public methods
double Student::Average() const
{
if (scores.size() > 0)
return scores.sum() / scores.size();
else
return 0;
}
const string& Student::Name() const
{
return name;
}
double& Student::operator[](int i)
{
return scores[i]; // use valarray::operator[]()
}
double Student::operator[](int i) const
{
return scores[i];
}
// private method
ostream& Student::arr_out(ostream& os) const
{
int i;
int lim = scores.size();
if (lim > 0)
{
for (i = 0; i < lim; i++)
{
os << scores[i] << " ";
if (i % 5 == 4)
os << endl;
}
if (i % 5 != 0)
os << endl;
}
else
os << " empty array ";
return os;
}
// friends
// use string version of operator>>()
istream& operator>>(istream& is, Student& stu)
{
is >> stu.name;
return is;
}
// use string friend getline(ostream &, const string &)
istream& getline(istream& is, Student& stu)
{
getline(is, stu.name);
return is;
}
// use string version of operator<<()
ostream& operator<<(ostream& os, const Student& stu)
{
os << "Scores for " << stu.name << ":\n";
stu.arr_out(os); // use private method for scores
return os;
}
main.cpp
// use_stuc.cpp -- using a composite class
// compile with studentc.cpp
#include
#include "studentc.h"
using std::cin;
using std::cout;
using std::endl;
void set(Student& sa, int n);
const int pupils = 3;
const int quizzes = 5;
int main()
{
Student ada[pupils] =
{ Student(quizzes), Student(quizzes), Student(quizzes) };
int i;
for (i = 0; i < pupils; ++i)
set(ada[i], quizzes);
cout << "\nStudent List:\n";
for (i = 0; i < pupils; ++i)
cout << ada[i].Name() << endl;
cout << "\nResults:";
for (i = 0; i < pupils; ++i)
{
cout << endl << ada[i];
cout << "average: " << ada[i].Average() << endl;
}
cout << "Done.\n";
return 0;
}
void set(Student& sa, int n)
{
cout << "Please enter the student's name: ";
getline(cin, sa);
cout << "Please enter " << n << " quiz scores:\n";
for (int i = 0; i < n; i++)
cin >> sa[i];
while (cin.get() != '\n')
continue;
}
运行结果:
Please enter the student's name: Jasmine
Please enter 5 quiz scores:
99
99
99
99
99
Please enter the student's name: Lily
Please enter 5 quiz scores:
99
99
99
99
99
Please enter the student's name: Booo
Please enter 5 quiz scores:
99
99
99
99
99
Student List:
Jasmine
Lily
Booo
Results:
Scores for Jasmine:
99 99 99 99 99
average: 99
Scores for Lily:
99 99 99 99 99
average: 99
Scores for Booo:
99 99 99 99 99
average: 99
Done.
D:\Prj\C++\Private_Inheritance\Debug\Private_Inheritance.exe (进程 1348)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
C++还有另一种实现has-a关系的途径–私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。我们将使用术语子对象(subobject)来表示通过继承或包含添加的对象。
class Student : private std::string, private std::valarray<double>
{
public:
...
};
第一个区别:容器版本提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员。
第二个区别:
Student(const char * str, const double * pd, int n)
: name(str), scores(pd, n) {} // use object names for containment
Student(const char * str, const double * pd, int n)
: std::string(str), ArrayDb(pd, n) {} // use class names for inheritance
总之,使用容器时将使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法。
使用作用域运算符可以访问基类的方法,但如果要使用基类对象本身,该如何作呢?
答案是使用强制类型转换。由于Student类是从string类派生而来的,因此可以通过强制类型转换,将Student对象转换为string对象;结果为继承而来的string对象。
const string & Student::Name() const
{
return (const string &) *this;
}
用类名显式地限定函数名不适合于友元函数,这是因为友元不属于类。然而,可以通过显式地转换为基类来调用正确的函数。
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for " << (const String &) stu << ":\n";
...
}
考虑以下代码:
os << "Scores for " << (const String &) stu << ":\n";
显式地将stu转换为string对象引用,进而调用函数operator<<(ostream&,const String&)。
引用stu不会自动转换为string引用。根本原因在于,在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。
另一个原因是,由于这个类使用的是多重继承,编译器将无法确定应转换成哪个基类,如果两个基类都提供了函数operator<<()。
studenti.h
#pragma once
// studenti.h -- defining a Student class using private inheritance
#ifndef STUDENTC_H_
#define STUDENTC_H_
#include
#include
#include
class Student : private std::string, private std::valarray<double>
{
private:
typedef std::valarray<double> ArrayDb;
// private method for scores output
std::ostream& arr_out(std::ostream& os) const;
public:
Student() : std::string("Null Student"), ArrayDb() {}
explicit Student(const std::string& s)
: std::string(s), ArrayDb() {}
explicit Student(int n) : std::string("Nully"), ArrayDb(n) {}
Student(const std::string& s, int n)
: std::string(s), ArrayDb(n) {}
Student(const std::string& s, const ArrayDb& a)
: std::string(s), ArrayDb(a) {}
Student(const char* str, const double* pd, int n)
: std::string(str), ArrayDb(pd, n) {}
~Student() {}
double Average() const;
double& operator[](int i);
double operator[](int i) const;
const std::string& Name() const;
// friends
// input
friend std::istream& operator>>(std::istream& is,
Student& stu); // 1 word
friend std::istream& getline(std::istream& is,
Student& stu); // 1 line
// output
friend std::ostream& operator<<(std::ostream& os,
const Student& stu);
};
#endif
studenti.cpp
// studenti.cpp -- Student class using private inheritance
#include "studenti.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
// public methods
double Student::Average() const
{
if (ArrayDb::size() > 0)
return ArrayDb::sum() / ArrayDb::size();
else
return 0;
}
const string& Student::Name() const
{
return (const string&)*this;
}
double& Student::operator[](int i)
{
return ArrayDb::operator[](i); // use ArrayDb::operator[]()
}
double Student::operator[](int i) const
{
return ArrayDb::operator[](i);
}
// private method
ostream& Student::arr_out(ostream& os) const
{
int i;
int lim = ArrayDb::size();
if (lim > 0)
{
for (i = 0; i < lim; i++)
{
os << ArrayDb::operator[](i) << " ";
if (i % 5 == 4)
os << endl;
}
if (i % 5 != 0)
os << endl;
}
else
os << " empty array ";
return os;
}
// friends
// use String version of operator>>()
istream& operator>>(istream& is, Student& stu)
{
is >> (string&)stu;
return is;
}
// use string friend getline(ostream &, const string &)
istream& getline(istream& is, Student& stu)
{
getline(is, (string&)stu);
return is;
}
// use string version of operator<<()
ostream& operator<<(ostream& os, const Student& stu)
{
os << "Scores for " << (const string&)stu << ":\n";
stu.arr_out(os); // use private method for scores
return os;
}
main.cpp
// use_stui.cpp -- using a class with private inheritance
// compile with studenti.cpp
#include
#include "studenti.h"
using std::cin;
using std::cout;
using std::endl;
void set(Student& sa, int n);
const int pupils = 3;
const int quizzes = 5;
int main()
{
Student ada[pupils] =
{ Student(quizzes), Student(quizzes), Student(quizzes) };
int i;
for (i = 0; i < pupils; i++)
set(ada[i], quizzes);
cout << "\nStudent List:\n";
for (i = 0; i < pupils; ++i)
cout << ada[i].Name() << endl;
cout << "\nResults:";
for (i = 0; i < pupils; i++)
{
cout << endl << ada[i];
cout << "average: " << ada[i].Average() << endl;
}
cout << "Done.\n";
return 0;
}
void set(Student& sa, int n)
{
cout << "Please enter the student's name: ";
getline(cin, sa);
cout << "Please enter " << n << " quiz scores:\n";
for (int i = 0; i < n; i++)
cin >> sa[i];
while (cin.get() != '\n')
continue;
}
运行结果:
Please enter the student's name: Jasmine
Please enter 5 quiz scores:
99
99
99
99
99
Please enter the student's name: Lily
Please enter 5 quiz scores:
99
99
99
99
99
Please enter the student's name: Booo
Please enter 5 quiz scores:
99
99
99
99
99
Student List:
Jasmine
Lily
Booo
Results:
Scores for Jasmine:
99 99 99 99 99
average: 99
Scores for Lily:
99 99 99 99 99
average: 99
Scores for Booo:
99 99 99 99 99
average: 99
Done.
D:\Prj\C++\Private_Inheritance\Debug\Private_Inheritance.exe (进程 1348)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
既然可以使用容器,也可以使用私有继承来建立has-a关系,那么应使用哪种方式呢?大多数C++程序员倾向于使用容器。
然而,私有继承提供的特性确实比容器多:
class Student : protected std::string,
protected std::valarray<double>
{...};
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。和私有私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承之间的主要区别便呈现出来了。使用私有继承时,第三代类将不能使用基类的接口。
隐式向上转换意味着无需进行显式类型转换,就可以将基类指针或引用用于派生类对象。
使用保护派生或私有派生时,基类的公有成员将成为保护成员或私有成员。假设要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。
double Student::sum() const // public Student method
{
return std::valarray<double>::sum(); // use privately-inherited method
}
另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明(就像名称空间那样)
来指出派生类可以使用特定的基类成员,即使采用的是私有派生。
class Student : private std::string, private std::valarray<double>
{
...
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
...
};
上述using声明使得 valarray::min()和valarray::max()可用,就像它们是Student的公有方法一样:
cout << "high score: " << ada[i].max() << endl;
注意,using声明只使用成员名–没有圆括号、函数特征标和返回类型。
MI描述的是有多个直接基类的类。与单继承一样,公有MI表示的也是is-a关系。例如,可以从Waiter类和Singer类派生出SingingWaiter类:
class SingingWaiter : public Waiter, public Singer {...};
请注意,必须使用关键字public来限定每一个基类。这是因为,除非特别指出,否则编译器将认为是私有派生:
class Singingwaiter:public waiter,Singer(...)://Singer is a private base
MI可能会给程序员带来很多新问题。其中两个主要的问题是:从两个不同的基类继承同名方法:从两个或更多相关基类那里继承同一个类的多个实例。
通常,这种赋值将把基类指针设置为派生对象中的基类对象的地址。但ed中包含两个Worker对象,有两个地址可供选择,所以应使用类型转换来指定对象:
SingingWaiter ed;
Worker * pw = &ed; // ambiguous
Worker * pw1 = (Waiter *) &ed; // the Worker in Waiter
Worker * pw2 = (Singer *) &ed; // the Worker in Singer
包含两个Worker对象拷贝还会导致其他的问题。然而,真正的问题是:为什么需要Worker对象的两个拷贝?
针对MI导致的基类多副本问题,C++引入了一种新技术—虚基类(virtual base class)。
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。
就是只有一个副本。
class Singer : virtual public Worker {...};
class Waiter : public virtual Worker {...};
class SingingWaiter: public Singer, public Waiter {...};
现在,SingingWaiter对象将只包含Worker对象的一个副本。从本质上说,继承的Singer和Waiter对象共享一个Worker对象,而不是各自引入自己的Worker对象副本。
对于非虚基类,初始化列表中唯一可以出现的构造函数是直接基类的构造函数。但是这些构造函数反过来可以将信息传递给它们的基类。
class A
{
int a;
public:
A(int n = 0) : a(n) {}
...
};
class B: public A
{
int b;
public:
B(int m = 0, int n = 0) : A(n), b(m) {}
...
};
class C : public B
{
int c;
public:
C(int q = 0, int m = 0, int n = 0) : B(m, n), c(q) {}
...
};
C类的构造函数只能调用B类的构造函数,而B类的构造函数只能调用A类的构造函数。
如果Worker是虚基类,则这种信息传递将不起作用。
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
: Waiter(wk,p), Singer(wk,v) {} // flawed
存在的问题是,自动传递信息时,将通过2条不同的途径(Waiter和Singer)将wk传递给Worker对象。为避免这种冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类。因此,上述构造函数将初始化成员panache和voice,但wk参数中的信息将不会传递给子对象Waiter。然而,编译器必须在构造派生对象之前构造基类对象组件;在上述情况下,编译器将使用Worker的默认构造函数。
如果不希望默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数。
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
: Worker(wk), Waiter(wk,p), Singer(wk,v) {}
上述代码将显式地调用构造函数worker(const Worker&)。请注意,这种用法是合法的,对于虚基类,必须这样做;但对于非虚基类,则是非法的。
假设要给SingingWaiter类扩展Show() 方法。
SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
newhire.Show(); // ambiguous
对于单继承,如果没有重新定义Show(),则将使用最近祖先中的定义。而在多重继承中,每个直接祖先都有一个Show()函数,这使得上述调用是二义性的。
可以使用作用域解析运算符来澄清编程者的意图:
SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
newhire.Singer::Show(); // use Singer version
然而,更好的方法是在SingingWaiter中重新定义Show(),并指出要使用哪个Show()。
void SingingWaiter::Show()
{
Singer::Show();
}
这种让派生方法调用基方法的方法对于单一继承来说工作得很好。但是,该方法忽略了Waiter组件,也可以通过调用Waiter版本来解决这个问题:
void SingingWaiter::Show()
{
Singer::Show();
Waiter::Show();
}
然而,这将显示姓名和ID两次,因为Singer::Show()和Waiter::Show()都调用了Worker::Show()。
如果解决呢?一种办法是使用模块化方式,而不是递增方式,即提供一个只显示Worker组件的方法和一个只显示Waiter组件或Singer组件(而不是Waiter和Worker组件)的方法。然后,在Singing Waiter::Show()方法中将组件组合起来。
void Worker::Data() const
{
cout << "Name: " << fullname << "\n";
cout << "Employee ID: " << id << "\n";
}
void Waiter::Data() const
{
cout << "Panache rating: " << panache << "\n";
}
void Singer::Data() const
{
cout << "Vocal range: " << pv[voice] << "\n";
}
void SingingWaiter::Data() const
{
Singer::Data();
Waiter::Data();
}
void SingingWaiter::Show() const
{
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}
而Data()方法只在类内部可用,作为协助公有接口的辅助方法。然而,使Data()方法成为私有的将阻止Waiter中的代码使用Worker::Data(),这正是保护访问类的用武之地。如果Data()方法是保护的,则只能在继承层次结构中的类中使用它,在其他地方则不能使用。
workermi.h
#pragma once
// workermi.h -- working classes with MI
#ifndef WORKERMI_H_
#define WORKERMI_H_
#include
class Worker // an abstract base class
{
private:
std::string fullname;
long id;
protected:
virtual void Data() const;
virtual void Get();
public:
Worker() : fullname("no one"), id(0L) {}
Worker(const std::string& s, long n)
: fullname(s), id(n) {}
virtual ~Worker() = 0; // pure virtual function
virtual void Set() = 0;
virtual void Show() const = 0;
};
class Waiter : virtual public Worker
{
private:
int panache;
protected:
void Data() const;
void Get();
public:
Waiter() : Worker(), panache(0) {}
Waiter(const std::string& s, long n, int p = 0)
: Worker(s, n), panache(p) {}
Waiter(const Worker& wk, int p = 0)
: Worker(wk), panache(p) {}
void Set();
void Show() const;
};
class Singer : virtual public Worker
{
protected:
enum {
other, alto, contralto, soprano,
bass, baritone, tenor
};
enum { Vtypes = 7 };
void Data() const;
void Get();
private:
const static char* pv[Vtypes]; // string equivs of voice types
int voice;
public:
Singer() : Worker(), voice(other) {}
Singer(const std::string& s, long n, int v = other)
: Worker(s, n), voice(v) {}
Singer(const Worker& wk, int v = other)
: Worker(wk), voice(v) {}
void Set();
void Show() const;
};
// multiple inheritance
class SingingWaiter : public Singer, public Waiter
{
protected:
void Data() const;
void Get();
public:
SingingWaiter() {}
SingingWaiter(const std::string& s, long n, int p = 0,
int v = other)
: Worker(s, n), Waiter(s, n, p), Singer(s, n, v) {}
SingingWaiter(const Worker& wk, int p = 0, int v = other)
: Worker(wk), Waiter(wk, p), Singer(wk, v) {}
SingingWaiter(const Waiter& wt, int v = other)
: Worker(wt), Waiter(wt), Singer(wt, v) {}
SingingWaiter(const Singer& wt, int p = 0)
: Worker(wt), Waiter(wt, p), Singer(wt) {}
void Set();
void Show() const;
};
#endif
workermi.cpp
// workermi.cpp -- working class methods with MI
#include "workermi.h"
#include
using std::cout;
using std::cin;
using std::endl;
// Worker methods
Worker::~Worker() { }
// protected methods
void Worker::Data() const
{
cout << "Name: " << fullname << endl;
cout << "Employee ID: " << id << endl;
}
void Worker::Get()
{
getline(cin, fullname);
cout << "Enter worker's ID: ";
cin >> id;
while (cin.get() != '\n')
continue;
}
// Waiter methods
void Waiter::Set()
{
cout << "Enter waiter's name: ";
Worker::Get();
Get();
}
void Waiter::Show() const
{
cout << "Category: waiter\n";
Worker::Data();
Data();
}
// protected methods
void Waiter::Data() const
{
cout << "Panache rating: " << panache << endl;
}
void Waiter::Get()
{
cout << "Enter waiter's panache rating: ";
cin >> panache;
while (cin.get() != '\n')
continue;
}
// Singer methods
const char * Singer::pv[Singer::Vtypes] = { "other", "alto", "contralto","soprano", "bass", "baritone", "tenor" };
void Singer::Set()
{
cout << "Enter singer's name: ";
Worker::Get();
Get();
}
void Singer::Show() const
{
cout << "Category: singer\n";
Worker::Data();
Data();
}
// protected methods
void Singer::Data() const
{
cout << "Vocal range: " << pv[voice] << endl;
}
void Singer::Get()
{
cout << "Enter number for singer's vocal range:\n";
int i;
for (i = 0; i < Vtypes; i++)
{
cout << i << ": " << pv[i] << " ";
if (i % 4 == 3)
cout << endl;
}
if (i % 4 != 0)
cout << '\n';
cin >> voice;
while (cin.get() != '\n')
continue;
}
// SingingWaiter methods
void SingingWaiter::Data() const
{
Singer::Data();
Waiter::Data();
}
void SingingWaiter::Get()
{
Waiter::Get();
Singer::Get();
}
void SingingWaiter::Set()
{
cout << "Enter singing waiter's name: ";
Worker::Get();
Get();
}
void SingingWaiter::Show() const
{
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}
main.cpp
// workmi.cpp -- multiple inheritance
// compile with workermi.cpp
#include
#include
#include "workermi.h"
const int SIZE = 5;
int main()
{
using std::cin;
using std::cout;
using std::endl;
using std::strchr;
Worker* lolas[SIZE];
int ct;
for (ct = 0; ct < SIZE; ct++)
{
char choice;
cout << "Enter the employee category:\n"
<< "w: waiter s: singer "
<< "t: singing waiter q: quit\n";
cin >> choice;
//This function returns the address of the first occurrence of the choice character value in the string "wstq"; the function returns the NULL pointer if the character isn’t found.
while (strchr("wstq", choice) == NULL)
{
cout << "Please enter a w, s, t, or q: ";
cin >> choice;
}
if (choice == 'q')
break;
switch (choice)
{
case 'w': lolas[ct] = new Waiter;
break;
case 's': lolas[ct] = new Singer;
break;
case 't': lolas[ct] = new SingingWaiter;
break;
}
cin.get();
lolas[ct]->Set();
}
cout << "\nHere is your staff:\n";
int i;
for (i = 0; i < ct; i++)
{
cout << endl;
lolas[i]->Show();
}
for (i = 0; i < ct; i++)
delete lolas[i];
cout << "Bye.\n";
return 0;
}
运行结果:
Enter the employee category:
w: waiter s: singer t: singing waiter q: quit
t
Enter singing waiter's name: Jamsine
Enter worker's ID: 1
Enter waiter's panache rating: 99
Enter number for singer's vocal range:
0: other 1: alto 2: contralto 3: soprano
4: bass 5: baritone 6: tenor
6
Enter the employee category:
w: waiter s: singer t: singing waiter q: quit
s
Enter singer's name: bobo
Enter worker's ID: 2
Enter number for singer's vocal range:
0: other 1: alto 2: contralto 3: soprano
4: bass 5: baritone 6: tenor
6
Enter the employee category:
w: waiter s: singer t: singing waiter q: quit
q
Here is your staff:
Category: singing waiter
Name: Jamsine
Employee ID: 1
Vocal range: tenor
Panache rating: 99
Category: singer
Name: bobo
Employee ID: 2
Vocal range: tenor
Bye.
D:\Prj\C++\Multiple_Inheritance\Debug\Multiple_Inheritance.exe (进程 14556)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .