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. 封装性:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/22473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bug记录——报了一堆xtr1common和yvals_core.h的错误

现象 今天使用VisualStdio2022时&#xff0c;突然出现了这样严重的报错&#xff0c;看得我一头雾水&#xff0c;而且无法启动VisualStdio2022的调试。 原因 发现NULL没法直接使用时&#xff0c;跟着提示添加了如下的头文件。 #include<cstddef>&#xff0c;是C标准库的头…

Blog项目切换Markdown编辑器———LayUI弹出层弹出写在页面的内容导致的各种bug

【2024.5.24回顾】 1 问题描述(描述完自己解决了…) 正常情况 点击添加文章按钮后&#xff0c;弹出文章编辑界面&#xff0c;如果用富文本功能编辑&#xff0c;则一切正常。可以多次打开、关闭 Markdown 如果在弹出层中点击了切换编辑器按钮&#xff0c;会成功切换为markd…

JDBC学习笔记(三)高级篇

一、JDBC 优化及工具类封装 1.1 现有问题 1.2 JDBC 工具类封装 V1.0 resources/db.properties配置文件&#xff1a; 工具类代码&#xff1a; 1.3 ThreadLocal 1.4 JDBC 工具类封装 V2.0 二、DAO封装及BaseDAO 工具类 2.1 DAO 概念 2.2 BaseDAO 概念 2.3 BaseDAO 搭建 2.4 Ba…

WPF前端:一个纯Xaml的水平导航栏

效果图&#xff1a; 代码&#xff1a; 1、样式代码&#xff0c;可以写在窗体资源处或者样式资源文件中 <Style x:Key"MenuRadioButtonStyle" TargetType"{x:Type RadioButton}"><Setter Property"FontSize" Value"16" />…

开源规则引擎LiteFlow项目应用实践

本文介绍基于开源规则引擎LiteFlow&#xff0c;如何开发规则设计器&#xff0c;在低代码平台中集成规则引擎&#xff0c;并在项目中实现应用的效果。由于低代码平台使用规则引擎实现了逻辑编排的需求&#xff0c;所以本文中的叫法为“逻辑设计”、“逻辑编排”、“逻辑流引擎”…

.NET IoC 容器(三)Autofac

目录 .NET IoC 容器&#xff08;三&#xff09;AutofacAutofacNuget 安装实现DI定义接口定义实现类依赖注入 注入方式构造函数注入 | 属性注入 | 方法注入注入实现 接口注册重复注册指定参数注册 生命周期默认生命周期单例生命周期每个周期范围一个生命周期 依赖配置Nuget配置文…

0基础学习区块链技术——推演猜想

在《0基础学习区块链技术——入门》一文中&#xff0c;我们结合可视化工具&#xff0c;直观地感受了下区块的结构&#xff0c;以及链式的前后关系。 本文我们将抛弃之前的知识&#xff0c;从0开始思考和推演&#xff0c;区块链技术可能是如何构思出来的。 去中心 在一般的思维…

浅谈配置元件之随机变量

浅谈配置元件之随机变量 1.概述 为了增强测试的真实性和多样性&#xff0c;JMeter 提供了多种配置元件来生成动态数据&#xff0c;其中“随机变量”(Random Variable) 就是一种常用的配置元件&#xff0c;用于生成随机数值、字符串等&#xff0c;以模拟不同用户请求中的变化参…

认识meta

目录 认识meta camera_metadata的存储结构 camera_metadata的基本操作 申请camera_metadata 增加entry 查找entry 更新entry 删除entry 对tag的查找操作 vendor_tag_ops和vendor_cache_ops是Andriod提供的接口 propertyID Camxhal3metadatautil.cpp文件理解 Initia…

Redisson 分布式锁 - RLock、RReadWriteLock、RSemaphore、RCountDownLatch(配置、使用、原理)

目录 前言 Redisson 分布式锁 环境配置 1&#xff09;版本说明 2&#xff09;依赖如下 3&#xff09;配置文件如下 4&#xff09;项目配置 RLock 1&#xff09;使用方式 2&#xff09;加锁解释 3&#xff09;加锁时手动设置时间 4&#xff09;加锁时&#xff0c;到…

揭秘FL Studio21.2.8中文版一键解锁音乐创作新境界!

在音乐制作的广阔天地里&#xff0c;随着技术的不断进步和数字音频工作站&#xff08;DAW&#xff09;软件的普及&#xff0c;越来越多的音乐爱好者和专业制作人开始涉足音乐创作的奇妙旅程。其中&#xff0c;FL Studio以其强大的功能、直观的操作界面和丰富的音色资源&#xf…

LED驱动IC:HC2106,1W升压型DC/DC白光LED驱动器HC2106系列,供应给大功率白光LED灯提供能源、恒流源

LED驱动IC&#xff1a; HC2106&#xff1a;1W升压型DC/DC白光LED驱动器HC2106系列 概述&#xff1a;HC2106系列芯片是针对LED应用设计的PFM 控制模式的开关型DC/DC 升压恒流芯片&#xff0c;通过外接电阻可使输出电流值恒定在0mA&#xff5e;500mA。 HC2106可以给一个、多个…

算法004:盛水最多的容器

这道题比较简单&#xff0c;使用双指针。 要求的是最大面积&#xff0c;对于一个水桶&#xff08;水杯来说&#xff09;&#xff0c;面积的算法是固定的&#xff0c;就是底乘以高。 在这个题中&#xff0c;我们把左边的位置设为left&#xff0c;右边的位置设为right&#xff…

一个月飙升 9k star!打破常规的 git 客户端

作为一名程序员&#xff0c;想必大家每天都要使用 git 来管理自己的代码吧。有些大佬喜欢使用命令行来进行 git 的操作&#xff0c;有些新入门的小白程序员则比较喜欢使用各种 git 客户端来可视化的管理代码&#xff0c;而有些程序员则喜欢使用 IDE 中集成的 git 功能来做代码的…

力扣234. 回文链表

给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true # Definition for singly-linked list. # c…

R语言探索与分析17-CPI的分析和研究

一、选题背景 CPI&#xff08;居民消费价格指数&#xff09;作为一个重要的宏观经济指标&#xff0c;扮演着评估通货膨胀和居民生活水平的关键角色。在湖北省这个经济活跃的地区&#xff0c;CPI的波动对于居民生活、企业经营以及政府宏观经济政策制定都具有重要的影响。因此&a…

打造卓越任务调度体系:实用攻略与技巧解析

写这篇文章&#xff0c;想和大家从头到脚说说任务调度&#xff0c;希望大家读完之后&#xff0c;能够理解实现一个任务调度系统的核心逻辑。 1 Quartz Quartz 是一款 Java 开源任务调度框架&#xff0c;也是很多 Java 工程师接触任务调度的起点。 下图显示了任务调度的整体流…

基于STC12C5A60S2系列1T 8051单片机实现一主单片机与一从单片机相互发送数据的RS485通信功能

基于STC12C5A60S2系列1T 8051单片机实现一主单片机与一从单片机相互发送数据的RS485通信功能的RS485通信功能 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机串口通信介绍STC12C5A60S2系列1T 8051单片机串口通信的结构基于STC12C5A60S2系列1T 8051单片机串…

zdppy_api 中间件请求原理详解

单个中间件的逻辑 整体执行流程&#xff1a; 1、客户端发起请求2、中间件拦截请求&#xff0c;在请求开始之前执行业务逻辑3、API服务接收到中间件处理之后的请求&#xff0c;和数据库交互&#xff0c;请求数据4、数据库返回数据5、API处理数据库的数据&#xff0c;然后给客户…

【第十一课】空间数据基础与处理——属性数据管理

一、前言 Arcgis分析离不开两大主体数据&#xff0c;一是空间&#xff0c;二是经济属性。在运用 Aecgis 进行分析时&#xff0c;经常会碰到一些涉及多要素的属性更改或填写&#xff0c; 如果按照普通的方法&#xff0c;每个属性进行修改或填写的话&#xff0c;工作量是很大的&…