C++第七章:继承与派生

一、类的派生与继承

类的继承,是新的类从已有类那里得到的已有的特性。原有的类称为基类父类
从已有类产生新类的过程就是类的派生产生的新类称为派生类子类

派生类的定义

在C++中,派生类的一般定义语法为:

cpp 复制代码
class 派生类名 : 继承方式1 基类名1, 继承方式2 基类名2, ..., 继承方式N 基类名N  
{  
    // 派生类自己的成员变量  
    // 派生类自己的成员函数  
};

一个派生类,可以同时有多个基类,这种情况称为多继承;

一个派生类只有一个直接基类的情况,称为单继承。

在类族中,直接参与派生出某类的基类称为直接基类,基类的基类甚至更高层的基类称为间接基类。

继承方式

继承方式规定了如何访问从基类继承的成员。继承方式关键字为:public,protected和private,分别表示公有继承、保护继承和私有继承。如果不显式地给出继承方式关键字,系统的默认值就认为是私有继承(private)。

派生类成员

派生类成员是指除了从基类继承来的所有成员之外,新增加的数据和函数成员。这些新增的成员,正是派生类不同于基类的关键所在,是派生类对基类的发展。这就是类在继承基础上的进化和发展。

派生类生成过程

派生新类这个过程,实际是经历了3个步骤:吸收基类成员、改造基类成员、添加新的成员。

  1. 在C++的类继承中,第一步是将基类的成员全盘接收,这样,派生类实际上就包含了它的全部基类中除构造和析构函数之外 的所有成员。在派生过程中构造函数和析构函数
    都不被继承
  2. 当派生类定义了一个与基类中具有相同名称的成员(无论该成员是函数还是变量)时。这种情况下,派生类中的成员会隐藏基类中的同名成员。重要的是要注意,这里的隐藏是基于名字的,与成员的类型或函数参数列表无关。即使派生类的成员函数与基类的成员函数参数列表不同 ,基类中的成员函数也会被隐藏
  3. 派生类新成员的加人是继承与派生机制的核心,是保证派生类在功能上有所发展的关键。由于在派生过程中,基类的构造函数和析构函数是不能被继承的,因此要实现一些特别的初始化和扫尾清理工作,就需要在派生类中加人新的构造和析构函数。

二、访问控制

从基类继承的成员,其访问属性由继承方式控制。基类的成员可以有public(公有)、protected(保护)和private(私有)三种访问属性。不同的继承方式,导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。这里说的访问来自两个方面:

  1. 一是 派生类中的新增成员 访问 从基类继承的成员;
  2. 二是在派生类外部(非类族内的成员),通过 派生类的对象 访问 从基类继承的成员。

下图总结了三种继承方式下 的访问权限:

三、类型兼容性规则

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。类型兼容规则中所指的替代包括以下的情况。

  1. 派生类的对象可以隐含转换为基类对象。
  2. 派生类的对象可以初始化基类的引用。
  3. 派生类的指针可以隐含转换为基类的指针。

在替代之后,派生类对象 就可以作为 基类的对象使用,但只能 使用 从基类继承的成员。

1. 派生类的对象可以隐含转换为基类对象

cpp 复制代码
#include <iostream>  
  
class Animal {  
public:  
    void eat() {  
        std::cout << "This animal eats food." << std::endl;  
    }  
};  
  
class Dog : public Animal {  
public:  
    void bark() {  
        std::cout << "Woof!" << std::endl;  
    }  
};  
  
int main() {  
    Dog myDog;  
    Animal myAnimal = myDog; // 隐含转换:Dog 对象被转换为 Animal 对象  
    myAnimal.eat(); // 正确,因为 Animal 有 eat 方法  
    // myAnimal.bark(); // 错误,因为 Animal 没有 bark 方法  
    return 0;  
}

在这个例子中,Dog 类的对象 myDog 被隐含转换为 Animal 类的对象 myAnimal。这允许我们通过基类类型的对象 myAnimal 调用从基类继承的方法 eat()

2. 派生类的对象可以初始化基类的引用

cpp 复制代码
#include <iostream>  
  
// 类定义与上面相同  
  
int main() {  
    Dog myDog;  
    Animal& myAnimalRef = myDog; // 使用 Dog 对象初始化 Animal 引用  
    myAnimalRef.eat(); // 正确,因为 Animal 有 eat 方法  
    // myAnimalRef.bark(); // 错误,因为 Animal 引用不能调用 Dog 的方法  
    return 0;  
}

在这个例子中,Dog 类的对象 myDog 被用来初始化一个 Animal 类型的引用 myAnimalRef。通过这个引用,我们只能访问 Animal 类中定义的方法。

3. 派生类的指针可以隐含转换为基类的指针

cpp 复制代码
#include <iostream>  
  
// 类定义与上面相同  
  
int main() {  
    Dog* myDogPtr = new Dog();  
    Animal* myAnimalPtr = myDogPtr; // 隐含转换:Dog* 转换为 Animal*  
    myAnimalPtr->eat(); // 正确,因为 Animal 有 eat 方法  
    // myAnimalPtr->bark(); // 错误,因为 Animal 指针不能调用 Dog 的方法  
    delete myDogPtr; // 释放内存  
    return 0;  
}

在这个例子中,Dog 类的指针 myDogPtr 被隐含转换为 Animal 类的指针 myAnimalPtr。通过这个指针,我们只能访问 Animal 类中定义的方法。注意,在删除动态分配的内存时,应该使用原始类型的指针(在这个例子中是 Dog*),以避免潜在的内存泄漏或未定义行为。

四、派生类的构造和析构函数

构造派生类的对象时,就要对基类的成员对象和新增成员对象进行初始化。基类的

构造函数并没有继承下来,要完成这些工作,就必须给派生类添加新的构造函数。

在构造派生类的对象时,会++首先调用基类的构造函数++ ,来++初始化它们的数据成员++ ,然后按照构造函数初始化列表中指定的方式初始化派生类新增的成员对象 ,++最后才执行派生类构造函数的函数体++ 。

派生类构造函数的一般语法形式为:

cpp 复制代码
派生类名::派生类名(参数列表) : 基类名(基类初始化参数列表),成员对象名1(成员对象1的初始化参数列表), 成员对象名2(成员对象2的初始化参数列表), ...  
{  
    // 派生类构造函数体  
    // 初始化派生类特有的成员变量  
}

派生类构造函数执行的一般次序如下。

  1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
  2. 对派生类新增的成员对象初始化,调用顺序按照它们在类中声明的顺序。
  3. 执行派生类的构造函数体中的内容。

派生类复制构造函数的一般语法形式为:注(other)

cpp 复制代码
派生类名::派生类名(const 派生类名& other) : 基类名(other), 成员对象名1(other.成员对象名1), 成员对象名2(other.成员对象名2), ...  
{  
    // 复制构造函数体  
    // 复制派生类特有的成员变量(如果需要的话,通常是通过赋值)  
    // 注意:这里一般不再直接初始化成员变量,因为初始化列表已经完成了  
    // 但可能需要进行一些额外的复制逻辑  
}

析构函数用系统默认的。

以下为例子:

例子:日历时间

cpp 复制代码
#include <iostream>

using namespace std;

class Clock
{
public:
    Clock(int h, int m, int s) : hour(h), minute(m), second(s)
    {
        cout << "Clock(int, int, int)" << endl;
    }

    Clock(const Clock &other) : hour(other.hour), minute(other.minute), second(other.second)
    {
        cout << "Clock(Clock &)" << endl;
    }

    ~Clock()
    {
        cout << "~Clock()" << endl;
    }

    void setTime(int h, int m, int s) //this
    {
        this->hour = h;
        minute = m;
        second = s;
    }

    void showTime() const// const Clock *this
    {
        cout << this->hour << ":" << this->minute << ":" << this->second << endl;
    }

protected:
    int hour;
    int minute;
    int second;
};


class CalendarClock : public Clock
{
public:
    CalendarClock(int y=0,int M=0,int d=0,int h=0,int m=0,int s=0) : Clock(h, m, s),year(y),month(M),day(d)
    {
        cout << "CalendarClock::CalendarClock()" << endl;
    }
    CalendarClock(const CalendarClock &other) : Clock(other),year(other.year),month(other.month),day(other.day)
    {
        cout << "CalendarClock::CalendarClock(const CalendarClock &)" << endl;
    }
    ~CalendarClock()
    {
        cout << "CalendarClock::~CalendarClock()" << endl;
    }

    void setTime(int y,int M,int d,int h,int m,int s)
    {
        year = y;
        month = M;
        day = d;
        Clock::setTime(h,m,s);//hour = h,minute = m,second = s;
    }
    void show() const
    {
        cout << year << "/" << month << "/" << day << " " << hour << ":" << minute << ":" << second << endl;
    }
private:
    int year;
    int month;
    int day;
};

int main(void)
{
   CalendarClock C(2024,8,9,9,58,4);
   C.show();

   CalendarClock C1(C);
   C1.show();

   CalendarClock C2;
   C2.setTime(2024,8,9,10,11,4);
   C2.show();

   Clock *p = &C2;
   p->showTime();
    
   return 0;
}

构造:先基类,后派生

相关推荐
可均可可8 分钟前
C++之OpenCV入门到提高004:Mat 对象的使用
c++·opencv·mat·imread·imwrite
杨荧24 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
白子寰30 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
小芒果_0135 分钟前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
gkdpjj41 分钟前
C++优选算法十 哈希表
c++·算法·散列表
王俊山IT43 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。1 小时前
c++多线程
java·开发语言
-Even-1 小时前
【第六章】分支语句和逻辑运算符
c++·c++ primer plus
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript