C++ - 面向对象编程方法

时间:2022-12-18 18:02:54

一、前言

“以对象为基础(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();
    }
}