5.2漫游:面向对象编程思维
程序编写:
接下来,让我们实现一个三层的类体系,并借此引入c++语言中基本组成和支持面向对象编程方法的语法元素。我以Libmat这个抽象基类作为类体系中最根本的类。我从Libmat派生出Book,并从Book中派生出Audiobook.
我们先限定接口只有一个constructor一个destructor和一个print()函数,我为每个member function加上一写程序代码,输出信息表示它们的存在,让我们得以跟踪程序的行为。
默认情形下,member fuction的解析(resolution)皆在编译时静态进行。若要另其在运行时动态进行,我们就得在它的声明前加上关键字virtual。Libmat的声明表示,其
destructor和print()皆为virtual(虚函数)
class Libmat
{
public:
Libmat()
{cout<<"Libmat::Libmat()
default constructor!\n";}
virtual ~Libmat()
{cout<<"Libmat::~Libmat()
default constructor!\n";}
virtual void print() const
{cout<<"Libmat::print()--i an a Libmat object!\n";}
};
现在我们定义一个non-member fuction print(),它接受一个参数,其形式为 const Libmat reference:
void print(const Libmat &mat)
{
cout<<"in global print():about to print mat.print()\n";
//下一行会依据mat实际指向的对象,解析应该执行哪一个print() member function.
mat.print();
}
我们在main()程序中重复调用print(),并依次将Libmat对象,BOOK对象,audiobook对象当做参数传给它。每次print()被执行,都会依据mat实际所指的对象,在Libmat,Book,Audiobook三者之间挑选正确的print()member function加以调用,第一次调用操作像下面这样:
cout<<'\n'<<"Creating a Libmat object to print()\n";
Libmat libmat;
print(libmat);
一下是跟踪结果:
creating a Libmat object to print()
//构造Libmat libmat
Libmat::Libmat() default constructor!
//处理print(libmat)
in global print():about to print mat.print()
Libmat::print()--i am a libmat object!
//析构libmat libmat
Libmat::~Libmat() destructor!
希望你不会对此感到任何惊讶。default constructor 的调用时紧跟在libmat的定义行为后。而在print()中,mat.print()会被解析为Libmat::print().接踵而来的便是Libmat destructor的调用。但是当我将Book对象传入print(),情况有点令人惊讶:
cout<<"\n"<<"creating a book object to print()\n";
Book b("the castle","franz kafka");
print(b);
以下便是经过注释的跟踪结果:
creating a book object to print()
//构造Book b
Libmat::Libmat() default constructor!
Book::Book(the castle,franz,kafka)constructor
//处理print(b)
in global print():about to print mat.print()
Book::print()--i am a book object!
my title is:the castle
my auther is:franz kafka
//析构Book b
Book::~Book() destructor!
Libmat::~Libmat()destructor!
第一个印象是,通过mat.print()所进行的虚拟调用(virtual invocation)操作的确有效。被调用的函数是Book::print()而非Libmat::print().第二个令人感兴趣的事是,当程序定义出一个派生对象,基类和派生类的constructor都会被执行。(当派生对象被销毁,基类和派生类的destructor也都会被执行。但次序颠倒)
我们该如何实现派生类Book呢?
为了清楚标示这个类是这个类乃是继承自一个已存在的类,其名称之后必须接一个冒号(:),然后紧跟着关键字public和基类的名称:
class Book:public Libmat{
public:
Book(const string &title,const string &auther)
:_title(title),_auther(auther)
{
cout<<"Book:Book("<<_title<<", "<<_auther<<")
constructor!\n";
}
virtual ~Book(){
cout<<"Book::Book() destructor!\n";
}
virtual void print()const{
cout<<"Book::print()--I am a Book object!\n";
<<"My title is:"<<_title<<'\n'
<<"My auther is:"<<_auther<<endl;
}
const string& title()
const {return _title;}
const string auther()
const {return _auther;}
protected:
string _title;
string _auther;
};
Book中的print()覆盖了(override)Libmat的print().这也正是mat.print()所调用的函数。title()和auther()是两个所谓的访问函数,都是non-virtual inline函数。过去我们不曾介绍关键字protected是的,被声明为protected所有成员函数都可以被派生类直接访问,除此(派生类)以外,都不得直接访问protected成员。
接下来,我从Book类派生出一个更特殊的audiobook类。audiobook除了拥有标题和作者,还有播讲者。在查看其实现之前,先让我把audiobook对象传给print():
cout<<"\n"<<"creating an audiobook object to print()\n";
audiobook ab("man without qualities","robert musil","kenneth meyer");
print(ab);
我们应该预期出现什么样的跟踪结果呢?我们应该预期:(1)通过mat.print()调用的是audiobook::print()(2)ab的构造过程乃是依次调用Libmat,Book,audiobook的constructor。一下即是跟踪结果:
creating a book object to print()
Libmat::Libmat() default constructor!
Book::Book(the castle,franz,kafka)constructor
//处理print(b)
in global print():about to print mat.print()
Book::print()--i am a book object!
my title is:the castle
my auther is:franz kafka
//析构Book b
Book::~Book() destructor!
Libmat::~Libmat()destructor!
该如何实现audiobook这个派生类呢?我们只需要把焦点放在audiobook与其基类book的不同之处——也就是print()——即可。当然,我们还必须提供audiobook播讲者姓名,以及这个类的constructor和destructor。至于book类所提供的各项数据及操作函数,均可被audiobook直接使用,仿佛它们本来便是由audiobook定义似的。
class audiobook:public book{
public:
audiobook(const string &title,const striing &auther,const string &narrator)
:book(title,auther),_narrator(narrator)
{
cout<<"audiobook::audiobook("_title<<","<<_auther<<","<<_narrator<<")constructor\n;
}
~audiobook()
{cout<<"audiobook::~audiobook() destructor!\n";}
virtual void print() const{
cout<<"audiobook::print()—i am an audiobook object!\n"
//注意,以下直接访问继承而来的
//打他member:_author,_title
<<"my title is:"<<_title<<'\n'
<<"my author is:"<<_narrator<<endl;}
const string& narrator() const{return _narrator;}
protected
string _narrator;
};
使用派生类时不必刻意区分“继承而来的成员”和“自身定义的成员”。两者的使用完全透明:
int main()
{
audiobook ab("mason and dixon","thomas pynchon","edwin leonard");
cout<<"the title is"<<ab.title()<<'\n'
<<"the author is:"<<ab.author()<<'\n'
<<"the narrator is:"<<ab.narrator()<<endl;}
}