主要特征:继承、多态
void loan_check_in(libmat &mat){
mat.check_in();
if(mat.is_late())
mat.assess_fine();
if(mat.waiting_list())
mat.notify_available();
}
继承体系最根本的就是抽象基类,抽象基类:带有纯虚函数,抽象类不可实例化,不可定义对象。
在面向对象的程序中,间接利用指向抽象基类的指针或引用来操作每一个类
让基类的指针或引用能够指向任何一个派生类的对象
编译器在编译时就依据mat所属的类决定执行哪一个check_in函数,由于在程序执行之前就已经解析出应该调用哪个函数,所以这种方式是静态绑定
在面向对象的编程中,编译器无法得知应该调用哪个check_in函数,每次loan_check_in执行时,只能在执行中根据mat所指的实际对象来确定应该调用哪个loan_check_in,解析操作是在运行时才进行,所以叫动态绑定
多态和动态绑定只有在使用指针和引用的时候才能发挥作用
我们通过libmat派生出book 再从book中派生出audiobook
默认情况下,成员函数都是在编译时静态进行,若要其在运行时动态进行,需要virtual关键字,
同时定义非成员函数,注意其中的形参是引用
#include
#include
#include
using namespace std;
class LibMat { //抽象基类,注意其中的析构函数和成员函数为虚函数,virtual表示其编译在运行时动态进行
public:
LibMat(){
cout << "LibMat::LibMat() default constructor!\n";
}
virtual ~LibMat(){
cout << "LibMat::~LibMat() destructor!\n";
}
virtual void print() const {
cout << "LibMat::print() -- I am a LibMat object!\n";
}
};
class Book : public LibMat { //Book的派生类
public:
Book( const string &title, const string &author )
: _title( title ), _author( author ){
cout << "Book::Book( " << _title
<< ", " << _author << " ) constructor\n";
}
~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 author is: " << _author << endl;
}
const string& title() const { return _title; }
const string& author() const { return _author; }
protected:
string _title;
string _author;
};
class AudioBook : public Book { //AudioBook派生类,继承了Book,注意构造函数调用Book的构造函数,在构造时,调用其基类Book的构造函数
public:
AudioBook( const string &title,
const string &author, const string &narrator )
: Book( title, author ), _narrator( narrator ){
cout << "AudioBook::AudioBook( " << _title
<< ", " << _author
<< ", " << _narrator
<< " ) constructor\n";
}
~AudioBook(){
cout << "AudioBook::~AudioBook() destructor!\n";
}
virtual void print() const {
cout << "AudioBook::print() -- I am a AudioBook object!\n"
<< "My title is: " << _title << '\n'
<< "My author is: " << _author << '\n'
<< "My narrator is: " << _narrator << endl;
}
const string& narrator() const { return _narrator; }
protected:
string _narrator;
};
void print( const LibMat &mat )
{
cout << "in global print(): about to print mat.print()\n";
mat.print();
}
int main()
{
// objects are in local blocks to force destruction
{
cout << "\n" << "Creating a LibMat object to print()\n";
LibMat m;
print( m );
}
{
cout << "\n" << "Creating a Book object to print()\n";
Book b( "The Castle", "Franz Kafka" );
print( b );
}
{
cout << "\n" << "Creating a AudioBook object to print()\n";
AudioBook ab( "Man Without Qualities", "Robert Musil", "Kenneth Meyer" );
print( ab );
}
return 0; // unnecessary but quiets vc++
}
程序结果为:
Creating a LibMat object to print()
LibMat::LibMat() default constructor!
in global print(): about to print mat.print()
LibMat::print() -- I am a LibMat object!
LibMat::~LibMat() destructor!
Creating a Book object to print()
LibMat::LibMat() default constructor!
Book::Book( The Castle, Franz Kafka ) constructor
in global print(): about to print mat.print()
Book::print() -- I am a Book object!
My title is: The Castle
My author is: Franz Kafka
Book::~Book() destructor!
LibMat::~LibMat() destructor!
Creating a AudioBook object to print()
LibMat::LibMat() default constructor!
Book::Book( Man Without Qualities, Robert Musil ) constructor
AudioBook::AudioBook( Man Without Qualities, Robert Musil, Kenneth Meyer ) constructor
in global print(): about to print mat.print()
AudioBook::print() -- I am a AudioBook object!
My title is: Man Without Qualities
My author is: Robert Musil
My narrator is: Kenneth Meyer
AudioBook::~AudioBook() destructor!
Book::~Book() destructor!
LibMat::~LibMat() destructor!
非成员函数的实参为抽象基类LibMat的对象,程序调用了抽象基类的虚函数print,mat.print在运行时被解析为LibMat::print(),同时也会调用抽象基类的构造函数和析构函数。
1.此时传入非成员函数的实参时派生类的Book的对象,mat.print所进行的虚拟调用有效,程序调用了Book::print(),而不是LibMat的成员函数。
2.同时基类和派生类的构造函数和析构函数均会被执行,调用了基类的默认构造函数。
3.Book中的print函数覆盖了基类中的同名函数,注意覆盖和重载的区别
4.同时关键字protected声明的成员可以被派生类访问,除了派生类外,都不能访问。
1.AudioBook中的构造函数,需要调用其基类Book中的构造函数,来对继承了成员变量进行初始化
2.程序运行先后调用了LibMat、Book、AudioBook的构造函数,同时print被解析为AudioBook::print,析构函数也会被执行,但是顺序和构造函数相反。
定义抽象类第一是要找到所有子类的共同操作,第二是找出哪些操作和类型有关。
虚函数:操作与类型有关的函数,即在不同类中的同名函数的功能不同
普通成员函数:具有共通的操作行为
class num_sequence {
public:
virtual ~num_sequence(){};
virtual num_sequence *clone() const = 0; //纯虚函数
virtual unsigned int elem( int pos ) const = 0; //纯虚函数
virtual const char* what_am_i() const = 0; //纯虚函数
static int max_elems(){ return _max_elems; }
virtual ostream& print( ostream &os = cout ) const = 0; //纯虚函数
protected:
virtual void gen_elems( int pos ) const = 0;
bool check_integrity( int pos, int size ) const;
const static int _max_elems = 1024;
};
每个虚函数,要么有定义,要么就被声明为纯虚函数
例如,当我使用
num_sequence *ps =new Fibonacci(12);
delete ps;
Fibonacci为其一个派生类,在delete时,我应该调用的是Fibonacci的析构函数,而不是num_sequence的,将析构函数定义为虚函数,可以使解析操作在运行时进行。同时,析构函数最好不要声明为纯虚函数
派生类必须为从基类继承来的每个纯虚函数提供定义,还要声明派生类的专属成员变量。
class Fibonacci : public num_sequence {
public:
Fibonacci( int beg_pos = 1, int len = 1 )
{ set_position( beg_pos ); set_length( len ); }
virtual unsigned int elem( int pos ) const;
virtual const char* what_am_i() const { return "Fibonacci"; }
virtual ostream& print( ostream &os = cout ) const;
virtual int length() const { return _length; }
virtual int beg_pos() const { return _beg_pos; }
protected:
virtual void gen_elems( int pos ) const;
static vector< unsigned int > _elems;
int _length;
int _beg_pos;
};
num_sequence *ps =new Fibonacci(12);
ps->what_am_i(); //通过虚函数机制,调用了Fibonacci::what_am_i()
ps->max_elems(); //调用继承来的num_sequence::max_elems()
ps->length(); //错误,length()不是num_sequence中的一员
ps->beg_pos(); //错误,beg_pos()不是num_sequence中的一员
delete ps //通过虚函数机制调用Fibonacci的析构函数
解决方法:1.在基类中加上两个纯虚函数length() 、beg_pos(),这样派生类中的就自动变成虚拟函数,不需要在指定关键字virtual
2.将length() 、beg_pos()移到基类中,派生类中就变成了了继承而来的非虚拟成员函数
inline int num_sequence::
elem( int pos ) const
{
if(! check_integrity(pos))
return 0;
if(pos>_elem.size())
Fibonacci::gen_elems(pos);
return _elems[pos-1];
}
如果在基类中又一个普通成员函数check_integrity,在派生类中也有一个check_integrity,如下程序:
class num_sequence {
public:
protected:
bool check_integrity( int pos, int size ) const;
};
class Fibonacci : public num_sequence {
public:
protected:
bool check_integrity( int pos, int size ) const;
};
inline int num_sequence::
elem( int pos ) const
{
if(! check_integrity(pos)) //此处调用的是Fibonacci的check_integrity
return 0;
}
每当派生类的某个成员和基类的成员重名的时候,便会遮挡住基类的那份成员,也就是派生类对该名称的任何使用,都会被解析为该派生类的那个成员,而不是继承来的成员
带来的问题是:在基类中check_integrity并未被视作虚函数,所以,每次通过基类的指针或引用来调用check_integrity,解析出来的都是基类的那个函数,并未考虑这个指针或引用指向的对象是什么。
void Fibonacci::example(){
num_sequence *ps=new Fibonacci(12,8);
ps->elem(1024); //通过虚拟机制动态解析为Fibonacci::elem()
ps->check_integrity(pos); //静态解析为num_sequence::check_integrity
}
所以在基类和派生类中提供同名的非虚拟函数,或许基类中所有的函数都应该被声明为virtual
引用永远无法代表空对象,指针可以指向null。
成员变量中,如果有引用,那么必须在构造函数中的成员初始化列表中将其初始化,如果是指针,则没这个要求
class num_sequence{
protected:
num_sequence(int len,int bp,vector&re):_length(len),_beg(bp),_relems(re){
}
int _length;
int _beg;
vector &_relems;
};
如果基类中存在实际的成员变量,必须为基类提供初始化,较好的方式是为基类提供构造函数。
num_sequence含有纯虚函数,是一个抽象基类,无法为其定义任何对象,num_sequence的作用是为每个派生类对象的子对象。所以,我们将基类中的构造函数声明为protected。
inline Fibonacci::Fibonacci(int len,int beg_pos):num_sequence(len,beg_pos,_elems){}
num_sequence::num_sequence(int len=1,int bp=1,vector *pe=0):_length(len),_beg_pos(bp),_pelems(pe){}
默认构造函数,虽然有形参,但是均有默认值,调用时不需要传入实参。
如果派生类没有指定调用基类的构造函数,编译器就会自动调用基类的默认构造函数。
在定义派生类的时候,必须决定,是将虚函数覆盖掉,还是继承虚函数,如果继承了纯虚函数,那么派生类也是抽象类。