1.包含
C++的一个主要目标就是促进代码复用。公有继承是实现复用的机制之一。但这并非唯一机制,还有其他机制:
(1)类成员本身是另一个类对象,这种方法称为包含(containment),组合(composition)或层次化(layering)。
(2)使用私有继承或保护继承。
以上两种方法,用于实现has-a关系,即新的类包含另一个类对象。
(3)函数模板,类模板。类模板使我们能够使用通用的术语定义类。
1.1包含对象成员的类--Student类
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
现在我们来设计一个student类,使用一个string类表示姓名,valarry<double>来表示分数。这就是运用到类包含了。
#ifndef _STUDENT_H_
#define _STUDENT_H_
#include<iostream>
#include<string>
#include<valarray>
class Student
{
private:
typedef std::valarray<double> ArrayDb;
std::string name; //contain object
ArrayDb scores; //contain object
//private method for scores output
std::ostream & arr_out(std::ostream & os)const;
public:
Student():name("Null Student"),scores(){}
Student(const std::string & s):name(s),scores(){}
explicit Student(int n):name("Nully"),scores(n){} //使用explict防止单参数构造函数的隐式转换
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 std::string &s, const double * pd, int n):name(s),scores(pd, n){}
~Student(){}
double average() const; //使用const限制方法修改数据,等等,根本原因:在编译阶段出现错误优于在运行阶段出现错误
const std::string & Name() const;
double & operator[](int i);
double operator[](int i) const;
friend std::istream & operator >> (std::istream & is, Student & stu); //必须是友元,才能访问name成员
friend std::istream & getline(std::istream & is, Student & stu);
friend std::ostream & operator << (std::ostream & os, const Student & stu);
};
#endif
下面对这个类的方法进行定义:
#include"student.h"
using std::ostream;
using std::istream;
using std::endl;
using std::string;
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];
}
double Student::operator[](int i) const
{
return scores[i];
}
ostream & Student::arr_out(ostream & os) const
{
int lim = scores.size();
if(lim > 0)
{
for(int i = 0; i < lim; i++)
{
os<<scores[i]<<" ";
if(i % 5 == 4)
os<<endl;
}
if(lim % 5 != 0)
os<<endl;
}
else
os<<"empty array";
return os;
}
istream & operator>>(istream & is, Student & stu)
{
is>>stu.name;
return is;
}
istream & getline(istream & is, Student & stu)
{
getline(is, stu.name);
return is;
}
ostream & operator<<(ostream & os, const Student & stu)
{
os<<"scores for"<<stu.name<<":\n";
stu.arr_out(os);
return os;
}
测试文件:
#include<iostream>
#include"student.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<<"\n Student list : \n";
for(i = 0; i < pupils; i++)
{
cout<<ada[i].Name()<<endl;
}
cout<<"\n Result:";
for(i = 0; i < pupils; i++)
{
cout<<endl<<ada[i]; //重载操作符
cout<<"averave: "<<ada[i].average()<<endl;
}
cout<<"Done\n";
while(1);
return 0;
/*
Please enter the student name:hongzong.lin
please enter 5 quiz scores:
99 88 77 66 100
Please enter the student name:dizong.lin
please enter 5 quiz scores:
99 11 99 100 222
Please enter the student name:bingzong.lin
please enter 5 quiz scores:
11 22 33 44 55
Student list :
hongzong.lin
dizong.lin
bingzong.lin
Result:
scores forhongzong.lin:
99 88 77 66 100
averave: 86
scores fordizong.lin:
99 11 99 100 222
averave: 106.2
scores forbingzong.lin:
11 22 33 44 55
averave: 33
Done
*/
}
void set(Student & sa, int n)
{
cout<<"Please enter the student 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;
}
上面这个例子,可以看到包括包含,重载操作符等,student通过自身方法调用包含类的方法,验证了获得实现,未获接口.
2.私有继承
现在再来看看has-a的另一种实现--->私有继承.私有继承使得基类的公有成员和保护成员都将成为派生类的私有成员.这说明基类方法不能成为派生对象的公有接口一部分.但可以在派生对象的成员函数中使用他们.而使用公有继承,派生类能够继承接口,是is-a关系的一部分.简而言着,私有继承,派生类不继承基类的接口,获得实现,但不获接口.这样的特性和包含相同.
2.2Student采用私有继承设计
#ifndef _STUDENTI_H_
#define _STUDENTI_H_
#include<iostream>
#include<string>
#include<valarray>
class Student:private std::string,private std::valarray<double>
{
private:
typedef std::valarray<double> ArrayDb;
std::ostream & arr_out(std::ostream & os) const;
public:
Student():std::string("NULL student"),ArrayDb(){}
Student(const std::string & s, int n):std::string(s),ArrayDb(n){} //使用类名进行声明
Student(int n):std::string("NULLY"),ArrayDb(n){}
Student(const std::string & s, const ArrayDb & a):std::string(s),ArrayDb(a){}
Student(const std::string & s, const ArrayDb n):std::string(s),ArrayDb(n){}
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;
friend std::istream & operator >>(std::istream & is, Student & stu);
friend std::istream & getline(std::istream & is, Student &stu);
friend std::ostream & operator <<(std::ostream & os, const Student & stu);
};
#endif
定义文件#include"studenti.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
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; //强制类型转换为string引用
}
double & Student::operator[](int i)
{
return ArrayDb::operator[](i); //使用类方法
}
double Student::operator[](int i) const
{
return ArrayDb::operator[](i);
}
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;
}
istream & operator>>(istream & is, Student & stu)
{
is>>(string & )stu;
return is;
}
istream & getline(istream & is, Student & stu)
{
getline(is, (string &)stu);
return is;
}
ostream & operator<<(ostream & os, const Student & stu)
{
os<<"Scores for "<<(const string &)stu<<":\n";
stu.arr_out(os);
return os;
}
再来看看测试文件:#include<iostream>
#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)};
for(int i = 0; i < pupils; i++)
{
set(ada[i], quizzes);
}
cout<<"\nstudent list:\n";
for(int i = 0; i < pupils; i++)
{
cout<<ada[i].Name()<<endl;
}
cout<<"\n results:";
for(int i = 0; i < pupils; i++)
{
cout<<endl<<ada[i];
cout<<"average "<<ada[i].average()<<endl;
}
cout<<"Done.\n";
while(1);
return 0;
/*
please enter the student name:
hongzong.lin
please eneter 5 quiz scores:
99 88 77 66 55
please enter the student name:
dizong.lin
please eneter 5 quiz scores:
99 88 77 66 44
please enter the student name:
bingzong.lin
please eneter 5 quiz scores:
99 88 77 55 22
student list:
hongzong.lin
dizong.lin
bingzong.lin
results:
Scores for hongzong.lin:
99 88 77 66 55
average 77
Scores for dizong.lin:
99 88 77 66 44
average 74.8
Scores for bingzong.lin:
99 88 77 55 22
average 68.2
Done.
*/
}
void set(Student & sa, int n)
{
cout<<"please enter the student name:\n";
getline(cin, sa);
cout<<"please eneter "<<n<<" quiz scores:\n";
for(int i = 0; i < n; i++)
{
cin>>sa[i];
}
while(cin.get() != '\n')
continue;
<h2>}</h2>
以上,对比下,我们是使用包含还是私有继承呢,这个需要具体问题具体分析,呵呵.废话.但大多数倾向于使用包含:(3)包含能够包括多个同类的子对象。比如需要3个string对象。
2.2保护继承
特征 | 公有继承 | 保护继承 | 私有继承 |
公有成员变成 | 派生类公有成员 | 派生类保护成员 | 派生类私有成员 |
保护成员变成 | 派生类保护成员 | 派生类保护成员 | 派生类私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能够隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
double Student::sum() const
{
return std::valarray<double>::sum();
}
B.使用using声明,指出派生类可以使用特定的基类成员,即使采用的是私有派生。class Student:private std::string,private std::valarray<double>
{
public:
using std::valarray<double>::min;
using std::valarray<double>::max; //注意,不用圆括号,呵呵
};
上述声明使得valarray<double>::min()/max() 像student的公有方法一样.:3.多重继承
我们来看一个例子,定义一个抽象基类Worker,并使用他派生出Waiter和Singer类.然后,使用MI从Waiter和Singer类派生出SingingWaiter类,这种情况下,Singer和Waiter都继承了一个Worker组件,因此,SingingWaiter将包含两个Worker组件.这将会导致问题,比如SingingWaiter ed;
worker * pw = &ed;
把派生类对象的地址赋给基类指针时,会导致二义性,因为ed中包含两个worker对象,有两个地址可供选择,他并不知道指向哪一个.所以应使用类型转换来指定对象:worker* pw1 = (Waiter*)&ed;
worker* pw2 = (Singer*)&ed;
这将使得使用基类指针来引用不同的对象(多态性)复杂化.MI引入了一种新技术,虚基类.virtual base class.3.1虚基类
class Singer:virtual public Worker{...};
class Waiter:public virtual Worker{...};//public virtual次序无关紧要
class SingingWaiter:public Singer,public Waiter {...};
从本质上来说,继承的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){m = m;}
};
class C:public B
{
int c;
public:
/*这里可以看出,C类调用B类的构造函数,B类自动回去调用A类的构造函数,而虚基类则禁止这样的自动调用*/
C(int q = 0, int m = 0, int n = 0):B(m, n){c = q;}
};
B.对于虚基类,像上面的这样自动调用A类构造函数的,将不生效.应该如下:SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other):Worker(wk),Waiter(wk, p),Singer(wk, v){}
对于虚基类,必须这样做,对于非虚基类,这是非法的.如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显示的调用该虚基类的某个构造函数.#if 1
#ifndef _MI_H_
#include<string>
class Worker
{
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:public virtual Worker//继承虚基类
{
private:
int panache;
protected:
void Get();
void Data()const;
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:public virtual Worker//继承虚基类
{
protected:
enum{other, alto, contrlto, soprano, bass, baritone, tenor};
enum{Vtypes = 7};
void Data()const;
void Get();
private:
static char *pv[Vtypes];
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;
};
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 & ws, int p = 0):Worker(ws),Waiter(ws, p),Singer(ws){}//有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的,上面已经看到.
void Set();
void Show()const;
};
#endif
#endif
类定义文件#include"mi.h"
#include<iostream>
using namespace std;
Worker::~Worker(){}
void Worker::Data()const //output emplyee name and id
{
cout<<"Name:"<<fullname<<endl;
cout<<"Employee ID:"<<id<<endl;
}
void Worker::Get() //input the employee name and id
{
getline(cin, fullname);
cout<<"Enter worker's ID:";
cin>>id;
while(cin.get() != '\n')
continue;
}
void Waiter::Set() //set worker and waiter
{
cout<<"Enter waiter's name:";
Worker::Get(); //input worker name and id
Get(); //input waiter panache
}
void Waiter::Show()const
{
cout<<"Category:waiter\n";
Worker::Data();
Data();
}
void Waiter::Data()const //only output waiter's member, not including the base class member
{
cout<<"pananche rating :"<<panache<<endl;
}
void Waiter::Get() //only input waiter's member,not including the base class member
{
cout<<"Enter waiter's panache rating: ";
cin>>panache;
while(cin.get() != '\n')
continue;
}
//singer method
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<<"Catagory: singer\n";
Worker::Data();
Data();
}
void Singer::Data()const //not including the base class member
{
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<<endl;
}
cin>>voice;
while(cin.get() != '\n')
continue;
}
void SingingWaiter::Data()const //only include singer and waiter,not including virtual class Worker
{
Singer::Data();
Waiter::Data();
}
void SingingWaiter::Get() //only include singer and waiter,not including virtual class Worker
{
Singer::Get();
Waiter::Get();
}
void SingingWaiter::Set()
{
cout<<"Enter singing waiter's name: ";
Worker::Get();
Get();
}
void SingingWaiter::Show()const //singingWaiter重写show().
{
cout<<"Category: singing waiter\n";
Worker::Data();
Data();
}
测试文件#include<iostream>
#include<cstring>
#include"mi.h"
const int SIZE = 5;
int main()
{
using namespace std;
Worker* lolas[SIZE];
int ct;
for(ct = 0; ct < SIZE; ct++)
{
char choice;
cout<<"Enter the employee category:\n"<<"w:waiter s:singer t:singingwaiter q:quit\n";
cin>>choice;
while(strchr("wstq", choice) == NULL)
{
cout<<"please enter a w, s, t, 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<<"\n here is your staff:\n";
for(int i = 0; i < ct; i++)
{
cout<<endl;
lolas[i]->Show();
}
for(int i = 0; i < ct; i++)
{
delete lolas[i];
}
cout<<"Bye. \n";
while(1);
return 0;
/*
Enter the employee category:
w:waiter s:singer t:singingwaiter q:quit
w
Enter waiter's name:hongzong.lin
Enter worker's ID:11
Enter waiter's panache rating: 11
Enter the employee category:
w:waiter s:singer t:singingwaiter q:quit
s
Enter singer's name: dizong.lin
Enter worker's ID:22
Enter number for singer's vocal range:
0:other 1:alto 2:contralto 3:soprano
4:bass 5:baritone 6:tenor
2
Enter the employee category:
w:waiter s:singer t:singingwaiter q:quit
t
Enter singing waiter's name: bingzai.lin
Enter worker's ID:33
Enter number for singer's vocal range:
0:other 1:alto 2:contralto 3:soprano
4:bass 5:baritone 6:tenor
3
Enter waiter's panache rating: 33
Enter the employee category:
w:waiter s:singer t:singingwaiter q:quit
q
here is your staff:
Category:waiter
Name:hongzong.lin
Employee ID:11
pananche rating :11
Catagory: singer
Name:dizong.lin
Employee ID:22
vocal range: contralto
Category: singing waiter
Name:bingzai.lin
Employee ID:33
vocal range: soprano
pananche rating :33
Bye.
*/
}