• C++多态和虚函数详解

    如果用不同类型的数据执行代码会产生不同的行为,那么该段代码就认为是多态的。例如,如果一个函数在传递不同类型的参数时执行不同的操作,那么称之为是多态的。

    为了说明多态性,请看下面的程序:

    //Inheritance4.h 的内容
    #include <string>
    #include <memory>
    using namespace std;
    
    enum class Discipline { ARCHEOLOGY, BIOLOGY, COMPUTER_SCIENCE };
    enum class Classification { FRESHMAN, SOPHOMORE, JUNIOR, SENIOR };
    class Person
    {
        protected:
            string name;
        public:
            Person() { setName(""); }
            Person(const string& pName) { setName(pName); }
            void setName(const string& pName) { name = pName; }
            string getName() const { return name; }
    };
    
    class Student:public Person
    {
        private:
            Discipline major;
            shared_ptr<Person> advisor;
        public:
            Student(const string& sname, Discipline d, const shared_ptr<Person>& adv) : Person(sname)
            {
                major = d;
                advisor = adv;
            }
            void setMajor(Discipline d) { major = d; }
            Discipline getMajor() const { return major; }
            void setAdvisor(shared_ptr<Person> p) { advisor = p; }
            shared_ptr<Person> getAdvisor() const { return advisor; }
    };
    
    class Faculty :public Person
    {
        private:
            Discipline department;
        public:
            Faculty(const string& fname, Discipline d) : Person(fname)
            {
                department = d;
            }
            void setDepartment(Discipline d) { department = d; }
            Discipline getDepartment() const { return department; }
    };
    
    class TFaculty : public Faculty
    {
        private:
            string title;
        public:
            TFaculty(const string& fname, Discipline d, string title):Faculty(fname, d)
            {
                setTitle(title);
            }
            void setTitle (const string& title) { this->title = title; }
            // Override getName()
            string getName() const
            {
                return title + " " + Person::getName();
            }
    
    };
    //main主程序
    // This exhibits the default non-polymorphic behavior of C++.
    #include "inheritance4.h"
    #include <vector>
    #include <iostream>
    using namespace std;
    int main()
    {
        // Create a vector of pointers to Person objects
        vector<shared_ptr<Person>> people
        {
            make_shared<TFaculty>
            ("Indiana Jones", Discipline::ARCHEOLOGY, "Dr."), make_shared<Student>("Thomas Cruise", Discipline::COMPUTER_SCIENCE, nullptr), make_shared<Faculty>("James Stock", Discipline::BIOLOGY),make_shared<TFaculty>("Sharon Rock", Discipline::BIOLOGY, "Professor"), make_shared<TFaculty>("Nicole Eweman", Discipline::ARCHEOLOGY, "Dr.")
        };
    
        // Print the names of the Person objects
        for (int k = 0; k < people.size (); k++)
        {
            cout << people[k]->getName() << endl;
        }
        return 0;
    }

    程序输出结果:

    Indiana Jones
    Thomas Cruise
    James Stock
    Sharon Rock
    Nicole Eweman

    该程序创建了一个矢量,这个矢量中的指针类型为 Student、Faculty 和 TFaculty,但实际上却指向了 Person 对象。然后它使用了相同的代码打印所有对象中名称。因为一个矢量只能存放一种类型的元素,所以必须使用一个指向基类的指针矢量。

    请注意,即使 TFaculty 对象具有自己的更特殊化的版本,程序也会为数组中的所有对象调用 Person 版本的 getName 函数。这段代码显然不是多态的,因为它为每个对象执行相同的成员函数,而不管它的类型如何。换句话说,对于不同类型的对象,其表现却并无不同。

    为了更好地理解所发生的事情,现在来仔细看一看这 5 次调用中每一次所发生的情况。

    people[k]->getName();

    该函数用于检索要打印的名称。在每次调用中,指向基类 Person 的指针 people[k] 用于调用不同派生类的对象中的 getName 函数。其中的一些类,如 TFaculty,提供了更特殊化的版本来覆盖 getName 函数。当 people[k] 指向 TFaculty 对象时,编译器必须在 Person 中定义的 getName 与 TFaculty 中定义的 getName 之间进行选择。Person 是指针所属的类,TFaculty 是对象实际所属的类。 

    前面已经介绍过,当一个指向基类的指针被用于访问被派生类覆盖的成员函数时,默认的 C++ 行为是使用指针所在类中定义的函数版本,而不是对象所属类中的版本。所以,这里编译器选择的是在 Person 中定义的 getName 函数,这也是函数的5次调用,虽然对象类型不同,但是表现却相同的原因。

    在面向对象编程中,通过基类指针调用派生类对象的成员函数的情况是很常见的。假设有一个基类 B,它有一个成员函数为基类指针 ptr 指向派生类 D 的一个对象。该示例语句如下:

    class B {
        public:
            void mfun()
            {
                cout << "Base class version";
            }
    };
    
    class D : public B
    {
        public:
            void mfun()
            {
                cout << "Derived class version";
            }
    };
    
    shared_ptr<Base> ptr = make_shared<D>();

    根据前面的介绍可知,此时如果写入以下语句,那么将被调用的应该是基类 B 的成员函数而不是派生类 D 的成员函数:

    ptr->mfun()

    如果想让编译器选择使用派生类 D 中更特殊化的 mfun() 版本,那该怎么办呢?在 C++ 中,可以通过将 mfun() 声明为基类中的虚函数来执行此操作。C++ 中使用虚函数来支持多态行为。因此,为了实现基类 B 及其所有派生类中 mfun() 函数的多态行为,必须修改基类 B 中的定义如下:

    class B
    {
        public:
        virtual void mfun()
        {
            cout << "Base class version";
        }
    };

    虚拟特性是可继承的。即如果派生类的成员函数覆盖了基类中的虚函数,那么该成员函数也会自动虚拟它本身。因此,在基类 B 中将 mfun 声明为虚函数,也将使 D 以及所有从 D 中派生的类中的 mfun 函数变成虚函数。

    虽然没有必要,但是很多程序员都会使用 virtual 关键字来标记所有的虚函数,以便更容易地识别它们。这是很好的做法,因此,D 的定义应该如下:

    class D : public B
    {
        public: virtual void mfun()
        {
            cout << "Derived class version";
        }
    };

    在本示例中,虚函数己经定义在类声明中。如果虚函数定义在类声明之外,则 virtual 关键字将继续对其类中声明有效,而对定义无效。C++ 不允许在类外面定义虚函数时加 virtual 关键字。

    以下程序是文章开头程序的修改版。其中,Person 类的 getName 函数己经声明为虚函数。它还包含了 inheritance5.h 文件,这是 inheritance4.h 文件的修改版,并且已经将 Person 类的 getName 函数变成了虚函数。

    //Inheritance5.h 的内容
    #include <string>
    #include <memory>
    using namespace std;
    
    enum class Discipline { ARCHEOLOGY, BIOLOGY, COMPUTER_SCIENCE };
    enum class Classification { FRESHMAN, SOPHOMORE, JUNIOR, SENIOR };
    class Person
    {
        protected:
            string name;
        public:
            Person() { setName(""); }
            Person(const string& pName) { setName(pName); }
            void setName(const string& pName) { name = pName; }
            //virtual function
            virtual string getName() const { return name; }
    };
    
    class Student:public Person
    {
        private:
            Discipline major;
            shared_ptr<Person> advisor;
        public:
            Student(const string& sname, Discipline d, const shared_ptr<Person>& adv) : Person(sname)
            {
                major = d;
                advisor = adv;
            }
            void setMajor(Discipline d) { major = d; }
            Discipline getMajor() const { return major; }
            void setAdvisor(shared_ptr<Person>& p) { advisor = p; }
            shared_ptr<Person> getAdvisor() const { return advisor; }
    };
    
    class Faculty :public Person
    {
        private:
            Discipline department;
        public:
            Faculty(const string& fname, Discipline d) : Person(fname)
            {
                department = d;
            }
            void setDepartment(Discipline d) { department = d; }
            Discipline getDepartment() const { return department; }
    };
    
    class TFaculty : public Faculty
    {
        private:
            string title;
        public:
            TFaculty(const string& fname, Discipline d,const string& title):Faculty(fname, d)
            {
                setTitle(title);
            }
            void setTitle (const string& title) { this->title = title; }
            // Override getName()
            string getName() const
            {
                return title + " " + Person::getName();
            }
    
    };
    //main主程序
    // This exhibits the default non-polymorphic behavior of C++.
    #include "inheritance5.h"
    #include <vector>
    #include <iostream>
    using namespace std;
    int main()
    {
        // Create a vector of pointers to Person objects
        vector<shared_ptr<Person>> people
        {
            make_shared<TFaculty>
            ("Indiana Jones", Discipline::ARCHEOLOGY, "Dr."), make_shared<Student>("Thomas Cruise", Discipline::COMPUTER_SCIENCE, nullptr), make_shared<Faculty>("James Stock", Discipline::BIOLOGY),make_shared<TFaculty>("Sharon Rock", Discipline::BIOLOGY, "Professor"), make_shared<TFaculty>("Nicole Eweman", Discipline::ARCHEOLOGY, "Dr.")
        };
    
        // Print the names of the Person objects
        for (int k = 0; k < people.size (); k++)
        {
            cout << people[k]->getName() << endl;
        }
        return 0;
    }

    程序输出结果:

    Dr. Indiana Jones
    Thomas Cruise
    James Stock
    Professor Sharon Rock
    Dr. Nicole Eweman

更多...

加载中...