C++多态公有继承

多态公有继承

多态

方法的行为应取决于调用该方法的对象,这种复杂的行为称为多态,具有多种形态,即同一种方法的行为随上下文而异。

实现多态公有继承

  1. 在派生类中重新定义基类的方法。
  2. 使用虚方法。

实例一

Person.h

#ifndef PERSON_H_
#define PERSON_H_

#include <string>

// 基类
class Person
{
    private:
        std::string m_firstname;
        std::string m_lastname;
    public:
        Person(const std::string& firstname, const std::string& lastname);
        Person() {}
        virtual ~Person() {}    // 虚析构函数
        std::string HGetName() const
        {
            return m_firstname + m_lastname;
        }
        virtual void HShowAll() const;  // 虚函数
        virtual Person& HChangeName(const std::string &lastname);   // 虚函数
};

// 派生类
class Student : public Person   // 公有继承
{
    private:
        int m_scores;
    public:
        Student(const std::string& firstname, const std::string& lastname, int scores = 0);
        Student(const Person &p, int scores = 0);
        Student();
        int HGetScores() const
        {
            return m_scores;
        }
        virtual void HShowAll() const;  // 重写虚函数
        virtual Student& HChangeName(const std::string &lastname);  // 重写虚函数,返回类型协变
};

#endif

#endif

Person.cpp

#include "Person.h"
#include <iostream>

Person::Person(const std::string& firstname, const std::string& lastname)
    :m_firstname(firstname), m_lastname(lastname)
{
}
Person& Person::HChangeName(const std::string &lastname)
{
    m_lastname = lastname;
    return *this; 
}
void Person::HShowAll() const
{
    std::cout << m_firstname << m_lastname;
}
Student::Student(const std::string& firstname, const std::string& lastname, int scores)
    :Person(firstname, lastname)    // 列表成员初始化
{
    m_scores = scores;
}
Student::Student()
    :Person(), m_scores(0)
{
}

Student::Student(const Person &p, int scores)
    :Person(p),m_scores(scores)
{
}
void Student::HShowAll() const
{
    Person::HShowAll();
    std::cout << ": " << m_scores;
}

Student& Student::HChangeName(const std::string &lastname)
{
    Person::HChangeName(lastname);
    return *this;
}

main.cpp

#include "Person.h"
#include <iostream>

using std::cout;
using std::endl;

int main()
{
    Person p1("li","si");
    p1.HShowAll();
    cout << endl;

    Student stu1(p1, 100);  // 调用Student(const Person &p, int scores = 0);
    stu1.HShowAll();
    cout << endl;

    Student stu2("zhang","san", 100);
    Person & p2 = p1;
    Person & p3 = stu2;


    p2.HShowAll();      // 调用Person::HShowAll();
    cout << endl;

    p3.HShowAll();    // 调用Student::HShowAll();
    cout << endl;

    // 指向Person的指针数组
    Person * all_person[3];
    all_person[0] = new Person("zhang","yi"); 
    all_person[1] = new Student("zhang","er", 90);
    all_person[2] = new Student("zhang","san");
    
    for(int i = 0; i < 3; i++)
	{
		all_person[i]->HShowAll();
		cout << endl;
		delete all_person[i];   // 将调用指针所指向的对象类型的析构函数
		all_person[i] = nullptr;
	}
	
	Person p4("li","si");
    p1.HShowAll();
    cout << endl;

    Person p5 = p1.HChangeName("yi");
    p2.HShowAll();
    cout << endl;

    Student stu3("zhang","san");
    stu3.HShowAll();
    cout << endl;

    Student stu4 = stu3.HChangeName("er");
    stu4.HShowAll();
    cout << endl;
    return 0;
}

程序说明

  1. Person类在声明HShowAll()时使用了关键字virtual,这个方法称为虚方法。
virtual void HShowAll() const;  // 虚函数
  • 如果不使用关键字virtual,程序将根据引用类型或指针类型选择方法;
  • 如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。
// 不将HShowAll()声明为虚函数
Person p1("li","si");
Student stu2("zhang","san", 100);
Person & p2 = p1;   // 引用类型为Person
Person & p3 = stu2; // 引用类型为Person

p2.HShowAll();      // 调用Person::HShowAll();
p3.HShowAll();      // 调用Person::HShowAll();

因此,如果要在派生类中重新定义基类的方法,则将它设置为虚方法;否则,设置为非虚方法。

  1. Person类将析构函数声明为虚析构函数。这样做是为了确保释放派生类对象时,按正确的顺序调用析构函数。
virtual ~Person() {}    // 虚析构函数
  • 如果析构函数不是虚的,则将只调用对应于指针类型的析构函数,例如实例一中将会只调用Person的析构函数;
  • 如果析构函数是虚的,将调用指针所指向的对象类型的析构函数。
  1. 派生类中调用基类的虚函数。使用作用域解析运算符来调用基类的方法
void Student::HShowAll() const
{
    Person::HShowAll();
    std::cout << ": " << m_scores;
}

虚函数工作原理

编译器会给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚函数表。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含了一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重写定义虚函数,该虚函数表将保存函数原始版本的地址。

哪些函数不能为虚函数,哪些函数应该为虚函数

  1. 构造函数不能是虚函数。创建派生类对象时,将先调用派生类的构造函数,然后再调用基类的构造函数,这种顺序不同于继承机制。
  2. 友元不能是虚函数。因为友元不是类成员,只有成员函数才能是虚函数。
  3. 析构函数应该是虚函数,防止派生类中的析构函数不被调用,造成内存泄漏。

返回类型协变

如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针,这种特性称为返回类型协变,它允许返回类型随类类型的变化而变化。

class Person
{
    ...
    virtual Person& HChangeName(const std::string &lastname);   // 虚函数
    ...
};
class Student : public Person
{
    ...
    virtual Student& HChangeName(const std::string &lastname);  // 重写虚函数,返回类型协变
    ...
};

访问控制:protected

  1. 关键字protected与private相似,在类外不能直接访问protected部分中的成员,只能通过类中的方法访问。
  2. 在继承方面,则派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。
  3. 对于外部来说,保护成员的行为与私有成员相似,但对于派生类来说,保护成员的行为与公有成员相似。
class Person
{
    private:    // 私有成员,外界不能访问,派生类不能直接访问
        std::string m_firstname;
    protected:  // 保护成员,外界不能访问,派生类可以直接访问
        std::string m_lastname;
    public:
        int m_age;  // 公有成员,外界可以访问,派生类可以直接访问
    ...
};

抽象基类

当类声明中包含纯虚函数时,则不能创建该类的对象,这样的类被声明为了抽象的。

C++通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾处为=0

实例二

Person.h

#ifndef PERSON_H_
#define PERSON_H_

#include <string>

// 基类
class Person
{
    private:
        std::string m_fullname;
    
    // 保护成员
    protected:
        int m_age;
        const std::string & HGetFullname() const
        {
            return m_fullname;
        }
    public:
        Person(const std::string fullname, int age)
            : m_fullname(fullname), m_age(age) {}
        Person() { m_age = 0; }
        virtual ~Person() {}    // 虚析构函数
        virtual void HFeture() const = 0;   // 纯虚函数
};

// 派生类
class Student : public Person   // 公有继承
{
    private:
        int m_scores;
    public:
        Student(const std::string& fullname, int age, int scores = 0);
        Student(const Person &p, int scores = 0);
        Student();
        virtual void HFeture() const;   // 必须重写纯虚函数
};
// 派生类
class Teacher : public Person    // 公有继承
{
    private:
        int m_classid;
    public:
        Teacher(const std::string& fullname, int age, int classid = 0);
        Teacher(const Person &p, int classid = 0);
        Teacher();
        virtual void HFeture() const;   // 必须重写纯虚函数
};
#endif

Person.cpp

#include "Person.h"
#include <iostream>

Student::Student(const std::string& fullname, int age, int scores)
    :Person(fullname, age), m_scores(scores)    // 列表成员初始化
{
}
Student::Student()
    :Person(), m_scores(0)
{
}

Student::Student(const Person &p, int scores)
    :Person(p),m_scores(scores)
{
}

void Student::HFeture() const
{
    std::cout << "My name is " << HGetFullname();   // 可以直接访问基类的保护成员 HGetFullname()
    std::cout << ". I'm " << m_age << " years old.";    // 可以直接访问基类的保护成员 m_age
    std::cout << ". I'm a student" << std::endl;
    std::cout << "I got " << m_scores << " points in this exam\n";
}

Teacher::Teacher(const std::string& fullname, int age, int classid)
    : Person(fullname, age), m_classid(classid)
{

}
Teacher::Teacher(const Person &p, int classid)
    : Person(p), m_classid(classid)
{
}
Teacher::Teacher()
    :Person(), m_classid(0)
{
}

void Teacher::HFeture() const   // 必须重写纯虚函数
{
    std::cout << "My name is " << HGetFullname();       // 可以直接访问基类的保护成员HGetFullname()
    std::cout << ". I'm " << m_age << " years old.";    // 可以直接访问基类的保护成员m_age
    std::cout << ". I'm a teacher" << std::endl;
    std::cout << "I am the head teacher of Class " << m_classid << std::endl;
}

main.cpp

#include "Person.h"
#include <iostream>

using std::cout;
using std::endl;

int main()
{
    Student stu1("zhangsan", 18, 90);
    stu1.HFeture();
    std::cout << std::endl;
    Teacher t1("lisi", 28, 2);
    t1.HFeture();
    return 0;
}

程序说明

  1. 保护成员:对于保护成员,外界不能直接访问。所以上述程序中m_ageHGetFullname()外界不能直接访问。
  2. 保护继承:派生类可以直接访问。所以对于m_ageHGetFullname(),派生类StudentTeacher可以直接访问。
  3. 纯虚函数:在基类中不用定义纯虚函数,而派生类必须重新定义纯虚函数。
  4. 抽象类:基类Person中有纯虚函数使Person成为了抽象类,抽象不能实例化。
virtual void HFeture() const = 0;   // 纯虚函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值