目录
一、前言
“以对象为基础(object-based)”的类机制无法针对如“are-a-kind-of(隶属同类)”的Book类的共通性质进行系统化的划分(如:RentalBook、AudioBook、InterativeBook)。因此,类间的关系有赖于“面向对象编程模型(object-oriented programming model)”加以设定。
二、面向对象编程
面向对象编程概念的两项最主要特质是:继承(inheritance)和多态(polymorphism)。前者使我们得以将一群相关的类组织起来,并让我们得以分享其间的共通数据和操作行为;后者让我们在这些类之上进行编程时,可以如同操控单一个体,而非相互独立的类,并赋予我们更多弹性来加入或移除任何特定类。
1)继承(inheritance)
继承机制定义了父子(parent/child)关系。父类(parent)定义了所有子类(children)共通的公有接口(public interface)和私有实现(private implementation)。每个子类都可以增加或覆盖(override)继承而来的东西,以实现其自身独特的行为。
在C++中,父类被称为基类(base class),子类被称为派生类(derived class)。父类和子类之间的关系则称为继承体系(inheritance hierarchy)。
2)多态(polymorphism)
让基类的pointer或reference得以十分透明的(transparently)指向其任何一个派生类的对象。
void function(LibMat &mat) {
// Libmat是图书管理系统的抽象基类。
// mat实际上代表某个派生类的对象(derived class object)。
// 如Book、RentalBook、Magazines,等等。
// mat必定指向程序中的某个实际对象。
// 被调用的check_in()函数被解析(resolved)为mat所代表的实际对象所拥有的那个check_in()函数。
mat.check_in();
/*其他操作*/
}
静态绑定(static binding):
程序执行之前就已经解析出应该调用哪一个函数。即编译器在编译时就依据mat所属的类决定应执行哪一个check_in()函数。
动态绑定(dynamic binding):
在面向对象编程方法中,编译器无法得知哪一份check_in()函数会被调用。每次function执行时,仅能在执行过程中依据mat所指的实际对象来决定调用哪一个check_in()。“找出实际被调用的究竟是哪一个派生类的check_in()函数”这一解析操作会延迟至运行时(run-time)才进行。
多态和动态绑定的特性只有在使用pointer或reference时才能发挥。
三、代码示例
#include <iostream>
class LibMat
{
public:
// constructor
LibMat() { std::cout << "LibMat::LibMat() default constructor!\n"; }
// destructor
virtual ~LibMat() { std::cout << "LibMat::~LibMat() destructor!\n"; }
virtual void print() const { std::cout << "LibMat::print() -- I am a LibMat object!\n"; }
};
// 基类可以public、protected或private三种方式继承而来。
class Book : public LibMat
{
public:
Book(const std::string &title, const std::string &author)
: _title(title), _author(author)
{
std::cout << "Book::Book( " << _title
<< ", " << _author << " ) constructor!\n";
}
virtual ~Book()
{
std::cout << "Book::~Book() destructor!\n";
}
// 覆盖(override)了LibMat的print()函数。
virtual void print() const
{
std::cout << "Book::print() -- I am a Book object!\n"
<< "My title is: " << _title << '\n'
<< "My author is: " << _author << std::endl;
}
const std::string &title() const { return _title; }
const std::string &author() const { return _author; }
// 被声明为protected的所有成员都可以被派生类直接访问,除此(派生类)之外,都不得直接访问protected成员。
protected:
std::string _title;
std::string _author;
};
class AudioBook : public Book
{
public:
AudioBook(const std::string &title,
const std::string &author, const std::string &narrator)
: Book(title, author), _narrator(narrator)
{
std::cout << "AudioBook::AudioBook( " << _title
<< ", " << _author
<< ", " << _narrator
<< " ) constructor!\n";
}
~AudioBook()
{
std::cout << "AudioBook::~AudioBook() destructor!\n";
}
virtual void print() const
{
std::cout << "AudioBook::print() -- I am an AudioBook object!\n"
<< "My title is: " << _title << '\n'
<< "My author is: " << _author << '\n'
<< "My narrator is: " << _narrator << std::endl;
}
const std::string &narrator() const { return _narrator; }
protected:
std::string _narrator;
};
void print(const LibMat &mat)
{
std::cout << "in global print(): about to print mat.print()\n";
// 依据mat实际指向的对象,解析应执行哪一个print() member function。
mat.print();
}
int main()
{
AudioBook ab("Mason and Dixon", "Thomas Pynchon", "Edwin Leonard");
std::cout << "The title is " << ab.title() << '\n'
<< "The author is " << ab.author() << '\n'
<< "The narrator is " << ab.narrator() << std::endl;
}
输出结果:
LibMat::LibMat() default constructor!
Book::Book( Mason and Dixon, Thomas Pynchon ) constructor!
AudioBook::AudioBook( Mason and Dixon, Thomas Pynchon, Edwin Leonard ) constructor!The title is Mason and Dixon
The author is Thomas Pynchon
The narrator is Edwin Leonard
AudioBook::~AudioBook() destructor!
Book::~Book() destructor!
LibMat::~LibMat() destructor!
四、为什么多态需要pointer和reference
在上述示例的基础上我们运行如下程序:
void print(LibMat object, const LibMat *pointer, const LibMat &reference)
{
std::cout << "object.print();" << std::endl;
object.print();
std::cout << "pointer->print();" << std::endl;
pointer->print();
std::cout << "reference.print();" << std::endl;
reference.print();
}
int main()
{
AudioBook ab("Mason and Dixon", "Thomas Pynchon", "Edwin Leonard");
print(ab, &ab, ab);
}
我们会得到如下输出:
object.print();
LibMat::print() -- I am a LibMat object!
pointer->print();
AudioBook::print() -- I am an AudioBook object!
My title is: Mason and Dixon
My author is: Thomas Pynchon
My narrator is: Edwin Leonard
reference.print();
AudioBook::print() -- I am an AudioBook object!
My title is: Mason and Dixon
My author is: Thomas Pynchon
My narrator is: Edwin Leonard
我们可以看到在LibMat object中虽然传入的对象时AudioBook但是调用的print()却是基类LibMat的print()函数。这是因为当我们声明一个基类的实例对象时,分配的内存空间不足以容纳我们传入的派生类对象,其子对象部分便被裁切(sliced off)了,只复制了基类的data member部分。因此多态(polymorphism)需要一层间接性。
五、获取派生类类名的方法
#include <typeinfo>
int main()
{
Book book("Mason and Dixon", "Thomas Pynchon");
LibMat *lm = &book;
std::cout << typeid(*lm).name() << std::endl;
// static_cast
if(typeid(*lm) == typeid(Book)) {
Book *b = static_cast<Book*>(lm);
b->print();
}
// dynamic_cast
if(Book *b = dynamic_cast<Book*>(lm)) {
b->print();
}
}