1.8 继承


返回目录 1 面向对象技术
上一节 1.7 cpp中类的常见特性
下一节 1.9 多态


概念:

继承(英语:inheritance)是面向对象软件技术当中的一个概念。

如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。

介绍:

继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。

在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。

另外,为子类别追加新的属性和方法也是常见的做法。

一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充。

特点:

基类成员 public protected private 不可访问
公有继承 public protected 不可访问 不可访问
保护继承 protected protected 不可访问 不可访问
私有继承 private private 不可访问 不可访问

单继承

接下来举个简单的单继承例子:

源代码

/* Extends.cpp,这是个单继承实例 */
#include <iostream>
#include <string>

/* 这个是基类,或者叫父类 */
class Animal
{
protected: // 保护类型,只能被自己或者自己的子类调用
    std::string name;
    std::string getSex() const; // 获取性别,const限制函数不能修改数据成员
    int getAge() const; // 获取年龄
private:
    std::string sex; // 性别
    int age; // 年龄
public:
    Animal(std::string name_l, std::string sex_l, int age_l)
        :name(name_l), sex(sex_l), age(age_l){} // 可以用这种方式为数据成员赋值
    ~Animal()
    {
        /* 或者直接在类中实现函数 */
    }
};

std::string Animal::getSex() const
{
    return sex; // 返回性别
}

int Animal::getAge() const
{
    return age; // 返回年龄
}

/* 这个叫派生类,或者叫子类 */
class Cat: public Animal
{
private:
    std::string words; // 存储叫声
public:
    Cat(std::string name_l, std::string sex_l, int age_l, std::string words_l)
        :Animal(name_l, sex_l, age_l), words(words_l){}; // 子类在构造函数中需要调用父类构造函数,除非父类有无参构造函数
    ~Cat(){};
    void speak() const; // 输出一段话
};

void Cat::speak() const
{
    std::cout << "我叫";
    std::cout << name << std::endl;
    std::cout << "性别是";
    // std::cout << sex << std::endl; // 不允许直接访问基类的私有成员
    std::cout << getSex() << std::endl; // 可以访问基类保护成员
    std::cout << "今年";
    std::cout << getAge() << "岁了" << std::endl;
    std::cout << "我的叫声是:";
    std::cout << words <<  std::endl; // 可以访问本类私有成员
}

int main()
{
    Cat cat("小花", "雌", 2, "喵喵喵");
    cat.speak();
    return 0;
}

编译运行

我叫小花
性别是雌
今年2岁了
我的叫声是:喵喵喵

例子中,我们创建了一个动物类,并且派生了一个猫类。

我们可以在猫类中使用到动物类的资源。

多重继承

源代码

/* MultipleExtends.cpp 多重继承实例 */
#include <iostream>
#include <string>

/* 接下来的一大片都是锦缎类的范围 */
class Brocade // 锦缎类
{
private:
    std::string words; // 锦缎的独白
public:
    Brocade(); // 构造函数:给锦缎的独白赋值
    ~Brocade(); // 析构函数:提示锦缎被销毁了
    void speak(); // 锦缎说话了!!!
};

Brocade::Brocade()
{
    this->words = "我是一条锦缎";
    std::cout << "【锦缎类对象已实例化!】" << std::endl;
}

Brocade::~Brocade()
{
    std::cout << "【锦缎类对象已被销毁!】" << std::endl;
}

void Brocade::speak()
{
    std::cout << words << std::endl;
}
/* 锦缎类结束 */

/* 鲤鱼类开始 */
class Carp // 鲤鱼类
{
private:
    std::string words; // 鲤鱼有话说
public:
    Carp(); // 构造函数:给鲤鱼有话说赋值
    ~Carp(); // 析构函数:提示鲤鱼被销毁了
    void speak(); // 当然,鲤鱼也能说话。。。(我不是泡泡)
};

Carp::Carp()
{
    this->words = "我是一只鲤鱼";
    std::cout << "【鲤鱼类对象已实例化!】" << std::endl;
}

Carp::~Carp()
{
    std::cout << "【鲤鱼类对象已被销毁!】" << std::endl;
}

void Carp::speak()
{
    std::cout << words << std::endl;
}
/* 鲤鱼类结束 */

/* 锦鲤类开始 */
class LuckyCharm: public Brocade, public Carp // 同时继承了锦缎和鲤鱼
{
private:
    std::string words; // 锦鲤的窃窃私语
public:
    LuckyCharm(std::string words_l):words(words_l)
    {
        std::cout << "【锦鲤类对象已实例化!】" << std::endl;
    };
    ~LuckyCharm();
    void speak();
};

LuckyCharm::~LuckyCharm()
{
    std::cout << "【锦鲤类对象已被销毁!】" << std::endl;
}

void LuckyCharm::speak()
{
    std::cout << words << std::endl;
}

/* 锦鲤类结束 */


int main()
{

{
    LuckyCharm lycm("锦鲤在此!谁敢不服?"); // 实例化锦鲤对象

    lycm.speak(); // 调用锦鲤对象的speak函数
    lycm.Brocade::speak(); // 调用锦鲤对象的父类锦缎对象的speak函数
    lycm.Carp::speak(); // 调用锦鲤对象的父类鲤鱼对象的speak函数
}

    return 0;

}   

编译运行

【锦缎类对象已实例化!】
【鲤鱼类对象已实例化!】
【锦鲤类对象已实例化!】
锦鲤在此!谁敢不服?
我是一条锦缎
我是一只鲤鱼
【锦鲤类对象已被销毁!】
【鲤鱼类对象已被销毁!】
【锦缎类对象已被销毁!】

这是一个简单的例子……

先忽略掉中间三个speak语句的输出。

【锦缎类对象已实例化!】
【鲤鱼类对象已实例化!】
【锦鲤类对象已实例化!】
……
【锦鲤类对象已被销毁!】
【鲤鱼类对象已被销毁!】
【锦缎类对象已被销毁!】

我们应该认识到,前三条输出语句和后三条输出语句分别代表着构造函数析构函数的运行,因为我在构造函数和析构函数里写了输出语句。

注意:

  • 构造函数是从父类开始运行,多重继承从最左边一个开始,到自己这个类结束;
  • 析构函数是从自己这个类开始,然后再父类,多重继承从最右边一个开始,到最左边结束。

也就是说构造和析构的顺序完全相反。

然后!

三个words分别属于三个类:

  • LuckyCharm::words
  • Brocade::words
  • Carp::words

三个words相互独立,如果我们直接使用words,是最外层的LuckyCharm类的words,其父类的words必须使用类名加上域作用符进行调用。

lycm.speak(); // 调用锦鲤对象的speak函数
lycm.Brocade::speak(); // 调用锦鲤对象的父类锦缎对象的speak函数
lycm.Carp::speak(); // 调用锦鲤对象的父类鲤鱼对象的speak函数

words是私有成员,无法直接访问,这里公有的speak()函数可以看出这一特点。

我们可以在锦鲤类中使用本类、锦缎类和鲤鱼类的资源。


返回目录 1 面向对象技术
上一节 1.7 cpp中类的常见特性
下一节 1.9 多态


参考资料:

  • 《C++程序设计》传智播客
  • 博客园
  • CSDN
  • 百度百科

冰河相依
1 声望3 粉丝

于冰河相依。