C++语言学习(七)—— 继承、派生与多态(一)

目录

一、派生类的概念

1.1 定义派生类的语法格式

1.1.1 定义单继承派生类

1.1.2 定义多继承派生类

1.2 继承方式

二、公有继承

三、派生类的构造和析构

四、保护成员的引入

五、改造基类的成员函数

六、派生类与基类同名成员的访问方式

七、私有继承和保护继承

7.1 私有继承

7.1.1 类组合与私有继承方式的主要区别

7.2 保护继承

7.3 保护继承与私有继承的异同点


一、派生类的概念

在C++中,派生类是指从另一个类(称为基类或父类)继承所有的成员(包括数据成员和成员函数)的新类。派生类继承了现有类的成员变量和成员函数,并且可以新增自己的成员变量和成员函数。派生类通过继承现有类的属性和行为,可以扩展现有类的功能或者在现有类的基础上进行修改,从而实现代码的复用和扩展。

派生类可以通过继承来获得基类的属性和行为,并且可以根据需要进行修改或扩展。通过继承,派生类可以重用基类的代码,避免了重复编写相似的代码。这样,派生类可以更加灵活和高效地实现特定的功能。

派生类通过使用派生类的名称来创建对象,并且可以使用派生类对象的成员函数来访问和修改继承的成员变量和成员函数派生类也可以重新定义继承的成员函数,称为函数的重写(override),从而修改现有类的行为。此外,派生类还可以定义自己特有的成员变量和成员函数,以实现自己的功能。

1.1 定义派生类的语法格式

1.1.1 定义单继承派生类

在C++中,定义单继承的派生类的语法格式如下:

class DerivedClass : access-specifier BaseClass {
   // 派生类的成员声明
};

其中,DerivedClass是派生类的名称,access-specifier访问权限修饰符,用于指定派生类对基类成员的访问权限,BaseClass是派生类所继承的基类。

访问权限修饰符有以下三种可能的取值:

  • public: 派生类可以访问基类的公有成员。
  • protected: 派生类可以访问基类的公有成员和保护成员,但不能访问私有成员。
  • private: 派生类不能访问基类的任何成员。

1.1.2 定义多继承派生类

在C++中,定义多继承的派生类的语法格式如下:

class DerivedClass : access-specifier BaseClass1, access-specifier BaseClass2, ..., access-specifier BaseClassN {
   // 派生类的成员声明
};

其中,DerivedClass是派生类的名称,access-specifier是访问权限修饰符,用于指定派生类对基类成员的访问权限,BaseClass1, BaseClass2, ..., BaseClassN是派生类所继承的多个基类,它们以逗号分隔。

以下是一个示例,定义了一个多继承的派生类DerivedClass,继承自基类BaseClass1BaseClass2

class BaseClass1 {
public:
   void baseFunction1() {
      // 基类1成员函数的实现
   }
};

class BaseClass2 {
public:
   void baseFunction2() {
      // 基类2成员函数的实现
   }
};

class DerivedClass : public BaseClass1, private BaseClass2 {
public:
   void derivedFunction() {
      // 派生类成员函数的实现
   }
};

在上面的示例中,派生类DerivedClass继承基类BaseClass1BaseClass2,并且可以访问BaseClass1的公有成员,但不能访问BaseClass2的任何成员。派生类还定义了自己的成员函数derivedFunction

1.2 继承方式

在C++中,派生类可以通过不同的继承方式来继承基类的成员。

C++中有三种继承方式,分别是:

  1. 公有继承(public inheritance):使用public关键字指定继承方式。公有继承意味着基类的公有成员在派生类中仍然是公有的,基类的保护成员在派生类中变为保护的,基类的私有成员在派生类中不可访问。

  2. 保护继承(protected inheritance):使用protected关键字指定继承方式。保护继承意味着基类的公有和保护成员在派生类中都变为保护的,基类的私有成员在派生类中不可访问。

  3. 私有继承(private inheritance):使用private关键字指定继承方式。私有继承意味着基类的公有和保护成员在派生类中都变为私有的,基类的私有成员在派生类中不可访问。

继承方式可以通过在派生类定义时使用相应的访问权限修饰符来指定。

以下是一个示例,展示了不同继承方式的使用:

class BaseClass {
public:
    void publicFunction() {
        // 公有成员函数实现
    }

protected:
    void protectedFunction() {
        // 保护成员函数实现
    }

private:
    void privateFunction() {
        // 私有成员函数实现
    }
};

class DerivedClassPublic : public BaseClass {
    // 此处定义的成员可以访问基类的公有成员和保护成员
};

class DerivedClassProtected : protected BaseClass {
    // 此处定义的成员可以访问基类的公有成员和保护成员
};

class DerivedClassPrivate : private BaseClass {
    // 此处定义的成员可以访问基类的公有成员和保护成员
};

在上面的示例中,DerivedClassPublic使用公有继承,DerivedClassProtected使用保护继承,DerivedClassPrivate使用私有继承。它们分别继承了BaseClass的公有成员和保护成员,并且在派生类中具有相应的访问权限。

二、公有继承

公有继承(public inheritance)是面向对象编程中的一种继承方式,它表示派生类可以继承基类的公有成员。在公有继承中,派生类可以访问基类的公有成员,包括公有的方法和属性,而对于基类的私有成员,派生类无法直接访问

使用公有继承时,派生类会继承基类的成员函数和成员变量,并且可以通过对象进行访问。派生类可以直接使用基类的公有成员,无需进行任何额外的操作。对于基类的公有成员,派生类可以直接使用,并且可以对其进行重载。此外,派生类也可以添加自己的成员函数和成员变量。

公有继承的特点包括:

  • 派生类可以访问基类的公有成员(方法和属性)
  • 派生类可以对基类的公有成员进行重载
  • 派生类可以添加自己的成员函数和成员变量

公有继承是面向对象编程中最常用的继承方式之一,它能够提供良好的代码复用性和扩展性,同时也符合了封装的原则。

注意:

  • 过度使用继承可能会导致类之间的耦合性增强,增加了代码的复杂性,因此在设计时需要谨慎使用公有继承。

三、派生类的构造和析构

派生类的构造函数和析构函数分别在创建和销毁派生类的对象时被调用。

构造函数用于初始化派生类对象的成员变量和调用基类的构造函数。在派生类的构造函数中,首先调用基类的构造函数来初始化继承的成员变量,然后再对派生类自身的成员变量进行初始化。如果派生类没有定义自己的构造函数,编译器会自动生成一个默认的构造函数。

析构函数用于销毁派生类对象时的清理工作。在派生类的析构函数中,先调用派生类自身的析构函数,然后再调用基类的析构函数。如果派生类没有定义自己的析构函数,编译器会自动生成一个默认的析构函数。

派生类的构造函数和析构函数的调用顺序是先基类的构造函数,然后派生类的构造函数,销毁对象时的顺序相反,先派生类的析构函数,然后基类的析构函数。

在派生类的构造函数和析构函数中,可以通过使用 关键字 " : : " 来调用基类的构造函数和析构函数。例如:

#include <iostream>
using namespace std;
class Base {
public:
    Base() {
        cout << "Base constructor called" << endl;
    }
    ~Base() {
        cout << "Base destructor called" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor called" << endl;
    }
    ~Derived() {
        cout << "Derived destructor called" << endl;
    }
};

int main() {
    Derived d;
    return 0;
}

输出结果为:

在这个例子中,派生类Derived继承了基类Base的构造函数和析构函数。创建Derived对象时,先调用基类Base的构造函数,然后再调用派生类Derived的构造函数。销毁Derived对象时,先调用派生类Derived的析构函数,然后再调用基类Base的析构函数。

四、保护成员的引入

当我们在派生类中需要直接访问基类的成员时,可以通过将这些成员声明为protected来引入保护成员。这样,派生类可以直接访问基类的成员,而不需要通过基类的公有接口来访问。

下面是一个示例,说明如何在派生类中引入保护成员:

class Base {
protected:
    int protectedMember; // 声明一个保护成员

public:
    Base(int value) : protectedMember(value) { } // 基类的构造函数初始化保护成员

    void display() {
        cout << "Protected member: " << protectedMember << endl;
    }
};

class Derived : public Base {
public:
    Derived(int value) : Base(value) { } // 派生类的构造函数调用基类的构造函数

    void updateProtectedMember(int value) {
        protectedMember = value; // 在派生类中直接访问基类的保护成员
    }
};

在上面的示例中,我们有一个基类Base和派生类Derived。在基类Base中,我们声明了一个protected成员protectedMember。在构造函数中,我们使用初始化列表来初始化保护成员。

在派生类Derived中,我们使用public继承了基类Base。由于protectedMember在基类中是保护的,因此在派生类中可以直接访问它。在派生类的成员函数updateProtectedMember中,我们直接对protectedMember进行赋值操作。在派生类的构造函数中,我们使用初始化列表来调用基类的构造函数以初始化保护成员。

通过引入保护成员,派生类Derived可以直接访问和修改基类Base的保护成员protectedMember,而不需要通过基类的公有接口来访问。这样可以简化代码,并提高代码的可读性和效率。

五、改造基类的成员函数

要改造基类的成员函数,可以通过在派生类中重新定义该成员函数来实现。

  1. 如果想在派生类中完全重写基类的成员函数,可以使用相同的函数名和参数列表,在派生类中重新实现该函数。这将会隐藏基类的成员函数,在派生类对象上调用该函数时将只执行派生类中的实现,不会执行基类中的实现。

  2. 如果想在派生类中对基类的成员函数进行扩展,可以使用相同的函数名和参数列表,在派生类中重新实现该函数,并在派生类中调用基类的成员函数。这样可以先执行基类的实现,再执行派生类的实现。

下面是一个示例代码:

class Base {
public:
    void foo() {
        // 基类的实现
        // ...
    }
};

class Derived : public Base {
public:
    void foo() {
        // 完全重写基类的成员函数
        // ...
    }
};

class Derived2 : public Base {
public:
    void foo() {
        // 对基类的成员函数进行扩展
        Base::foo(); // 调用基类的成员函数
        // 扩展派生类的实现
        // ...
    }
};

六、派生类与基类同名成员的访问方式

派生类中存在与基类同名的成员时,可以使用以下几种方式来访问这些成员

  • 使用作用域解析运算符(: :):可以使用基类名加上作用域解析运算符来访问基类中的同名成员。
class Base {
public:
    int data;
};

class Derived : public Base {
public:
    void accessBaseMember() {
        Base::data = 10; // 使用作用域解析运算符来访问基类的成员
    }
};

  • 使用this指针:this指针指向当前对象,可以用来访问当前对象中的成员变量和成员函数。在派生类中,可以使用this指针来访问基类的同名成员。
class Base {
public:
    int data;
};

class Derived : public Base {
public:
    void accessBaseMember() {
        this->data = 10; // 使用this指针来访问基类的成员
    }
};

  • 使用基类名进行访问:使用派生类对象的基类部分来访问基类的同名成员。
class Base {
public:
    int data;
};

class Derived : public Base {
public:
    void accessBaseMember() {
        Base::data = 10; // 使用基类名进行访问基类的成员
    }
};

  • 直接访问:如果派生类中没有与基类同名的成员变量,可以直接访问基类的同名成员。
class Base {
public:
    int data;
};

class Derived : public Base {
public:
    void accessBaseMember() {
        data = 10; // 直接访问基类的成员
    }
};

七、私有继承和保护继承

7.1 私有继承

私有继承是一种继承方式,它使得基类的公有成员和保护成员在派生类中变为私有成员,而私有成员在派生类中不可访问。

私有继承的语法格式为:

class Derived : private Base {};        //Derived是派生类,Base是基类。

私有继承的特点包括:

  1. 基类的公有成员和保护成员在派生类中变为私有成员,只能在派生类内部访问。
  2. 派生类对象不能直接访问基类的成员函数和成员变量,需要通过派生类的成员函数间接访问。
  3. 派生类可以重写基类的虚函数,但由于私有继承的特性,基类的虚函数在派生类中也变为私有成员,无法被外部访问。
  4. 私有继承主要用于实现组合关系,将基类的实现细节隐藏在派生类中,提供了一种方式来重用基类的实现。

私有继承在设计和实现中的常见应用包括:

  1. 实现封装:通过私有继承,派生类可以从基类继承私有成员,从而实现对基类实现细节的封装。
  2. 实现子类化:通过私有继承,派生类可以重用基类的实现,并添加自己的功能和行为。
  3. 实现特化:通过私有继承,可以对基类进行特化,使派生类专门适用于某些特定的场景或需求。

7.1.1 类组合与私有继承方式的主要区别

类组合和私有继承是实现代码复用的两种不同方式。

  1. 类组合:

    • 类组合是一种对象关系,其中一个类(成员对象)是另一个类的成员。
    • 在类组合中,一个类(组合类)包含另一个类(成员类)的对象作为它的成员变量。
    • 组合类可以通过调用成员类的公有成员函数来访问成员类的功能和行为。
    • 组合关系通常是“一个拥有另一个”的关系,可以灵活地在运行时更换成员对象。
  2. 私有继承:

    • 私有继承是一种继承关系,派生类继承基类的实现和接口。
    • 在私有继承中,派生类通过私有继承基类,将基类的公有成员和保护成员变为私有成员。
    • 私有继承将基类的实现细节隐藏在派生类中,派生类通过自身的公有接口来访问基类的功能。
    • 私有继承通常是“是一个”的关系,派生类是基类的一种特化或子类。

主要区别:

  1. 访问权限:在私有继承中,派生类只能在自己的成员函数中访问基类的成员,而在类组合中,组合类可以直接访问成员类的公有成员。
  2. 关系类型:类组合是一种对象关系,表示一个类拥有另一个类的对象作为成员;私有继承是一种继承关系,表示派生类是基类的一种特化或子类。
  3. 灵活性:类组合具有更大的灵活性,可以在运行时更换成员对象;私有继承在编译时确定基类类型,不可更换。

7.2 保护继承

保护继承是一种继承方式,它指的是在继承关系中,子类可以继承父类的成员(属性和方法),但不能访问父类的私有成员。

保护继承的目的是为了保护父类的实现细节,避免子类对父类的私有属性和方法进行直接访问和修改,从而提高代码的安全性和可维护性。

为了实现保护继承,可以在父类中使用访问修饰符来限制成员的访问权限。在C++中,可以使用protected关键字来修饰父类的成员,表示这些成员可以被子类访问,但不能被外部类或对象访问。

以下是一个使用保护继承的简单示例:

class Parent {
protected:
    int protectedMember;

public:
    void publicMethod() {
        // 父类的公有方法
    }

protected:
    void protectedMethod() {
        // 父类的保护方法
    }
};

class Child : public Parent {
public:
    void childMethod() {
        protectedMember = 10;  // 子类可以访问父类的保护成员
        protectedMethod();  // 子类可以调用父类的保护方法
    }
};

int main() {
    Child child;
    child.publicMethod();  // 子类可以调用父类的公有方法
    child.childMethod();   // 子类自己的方法
    // child.protectedMember;  // 无法访问父类的保护成员,会编译错误
    // child.protectedMethod();  // 无法调用父类的保护方法,会编译错误
    return 0;
}

在这个示例中,Parent类有一个保护成员protectedMember和一个保护方法protectedMethod,它们都可以被子类Child访问和调用。但在main函数中,无法直接访问protectedMember和protectedMethod,只能通过调用子类的方法间接访问。

7.3 保护继承与私有继承的异同点

保护继承和私有继承是两种不同的继承方式,在继承关系中有一些异同点。

异同点如下:

  1. 访问权限:

    • 保护继承:子类可以访问父类的受保护成员和公有成员,但不能访问私有成员。
    • 私有继承:子类不能直接访问父类的任何成员,包括公有、受保护和私有成员。
  2. 外部访问:

    • 保护继承:子类的对象不能访问父类的受保护和私有成员,只能通过子类的接口进行访问。
    • 私有继承:子类的对象不能访问父类的任何成员,包括公有、受保护和私有成员。
  3. 类型转换:

    • 保护继承:子类对象可以隐式转换为父类对象,但不能隐式转换为父类子类的对象。
    • 私有继承:子类对象不能进行隐式类型转换。
  4. 封装性:

    • 保护继承:保护继承提供了一种方式来封装父类的实现细节,并且可以在子类中使用父类的接口进行操作,提高了代码的封装性和安全性。
    • 私有继承:私有继承将父类的所有成员都隐藏在子类的内部,外部无法访问父类的任何成员,实现了更强的封装性。​​​​​​​

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-06-07 23:06:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-07 23:06:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-07 23:06:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-07 23:06:04       18 阅读

热门阅读

  1. 51.线程池大小

    2024-06-07 23:06:04       7 阅读
  2. Switch刷机:安装Android系统和Linux系统

    2024-06-07 23:06:04       18 阅读
  3. 【微信小程序】处理蓝牙数据相关函数

    2024-06-07 23:06:04       7 阅读
  4. 聊聊App在安卓设备中所使用的内存

    2024-06-07 23:06:04       9 阅读
  5. 【debian】常用指令

    2024-06-07 23:06:04       7 阅读
  6. 2024全国高考作文题解读(Chat GPT 4.0版本)

    2024-06-07 23:06:04       11 阅读
  7. 使用Python的xml.etree.ElementTree模块解析XML文件

    2024-06-07 23:06:04       6 阅读