C++中如何实现接口继承与实现继承,以及它们的区别?

概念

在 C++ 中,接口继承和实现继承是两种不同的继承方式,它们在设计模式、代码复用和多态性方面有着不同的应用。下面将分别解释这两者的概念、实现方式及其区别。

接口继承

接口继承指的是只继承类的接口(即公共的成员函数声明)而不实现这些函数。通常通过纯虚函数来实现。这样做的目的是约定一个行为规范,而具体的实现则留给派生类去完成。
示例:

#include <iostream>  

// 定义一个接口  
class Drawable {  
public:  
    virtual void draw() const = 0; // 纯虚函数  
    virtual ~Drawable() {}          // 虚析构函数以保证正确的析构  
};  

class Circle : public Drawable {  
public:  
    void draw() const override {  
        std::cout << "Drawing a circle." << std::endl;  
    }  
};  

class Square : public Drawable {  
public:  
    void draw() const override {  
        std::cout << "Drawing a square." << std::endl;  
    }  
};  

void render(const Drawable& shape) {  
    shape.draw(); // 调用接口的实现  
}  

int main() {  
    Circle circle;  
    Square square;  

    render(circle); // 输出: Drawing a circle.  
    render(square); // 输出: Drawing a square.  

    return 0;  
}
  • 输出
Drawing a circle.  
Drawing a square.

代码解析

  • 接口定义
class Drawable {  
public:  
    virtual void draw() const = 0; // 纯虚函数  
    virtual ~Drawable() {}          // 虚析构函数以保证正确的析构  
};
  • 类 Drawable 是一个接口类,使用大写字母开头的名称有助于表明它是一个接口。

  • virtual void draw() const = 0; 声明了一个纯虚函数 draw(),这意味着 Drawable 类没有实现此函数,任何派生类必须实现这个函数。这里的 = 0 表示这是一个纯虚函数,任何包含它的类都被视为抽象类,无法直接实例化。

  • virtual ~Drawable() {} 是一个虚析构函数,允许使用基类指针删除派生类实例时确保资源正确释放。这是接口设计的良好实践。

  • 实现接口的派生类

class Circle : public Drawable {  
public:  
    void draw() const override {  
        std::cout << "Drawing a circle." << std::endl;  
    }  
};
  • class Circle : public Drawable 表示 Circle 类继承自 Drawable 接口,派生类必须实现所有的纯虚函数。
  • void draw() const override 这是对基类 Drawable 中 draw() 的实现。通过 override 关键字指示这是对基类虚函数的重写。
  • 在 draw() 方法中,我们使用 std::cout 输出 “Drawing a circle.”。
class Square : public Drawable {  
public:  
    void draw() const override {  
        std::cout << "Drawing a square." << std::endl;  
    }  
};
  • Square 类实现与 Circle 类类似,只是输出的信息不同。它同样继承了 Drawable 接口并实现 draw() 方法。

  • 渲染函数

void render(const Drawable& shape) {  
    shape.draw(); // 调用接口的实现  
}
  • render 函数 接受一个常量引用类型的 Drawable 对象,这允许传入任何实现了 Drawable 接口的对象。

  • 在函数内部调用 shape.draw(),通过多态的机制,这将调用传入对象的具体 draw() 实现,打印出形状信息。

  • 主函数

int main() {  
    Circle circle;  
    Square square;  

    render(circle); // 输出: Drawing a circle.  
    render(square); // 输出: Drawing a square.  

    return 0;  
}
  • Circle circle; 和 Square square; 创建了 Circle 和 Square 对象。
  • render(circle); 和 render(square); 调用了 render 函数,分别传入 circle 和 square 对象。
  • 由于 render 函数使用了接口类型 Drawable 的引用,因此可以通过多态机制实现适当的函数调用。

关键点

  • Drawable 类是一个接口,它定义了一个纯虚函数 draw()。
  • Circle 和 Square 类实现了接口,重写了 draw() 函数。
  • 可以通过接口类型的引用或指针来实现多态。

总结

  • 接口继承的灵活性:通过使用抽象类(接口),我们可以定义统一的行为规范(draw())。不同的实现类(Circle 和 Square)可以各自实现该接口的函数,从而提供特定的行为。这种设计模式使得代码的可扩展性和可维护性得以提高。
  • 多态性:使用指向接口类的指针或引用可以实现多态,允许我们在不修改 render 函数的情况下轻松添加新的形状类。
  • 虚构造和释放:使用虚析构函数确保在删除基类指针时能够正确地调用派生类的析构函数,从而避免内存泄露。

实现继承

实现继承指的是继承一个类的完整实现,而不仅仅是接口。这意味着子类不仅可以使用父类的方法和属性,还可以访问和重用父类的实现。这种方式通常在需要共享代码的场景中使用。

示例:

#include <iostream>  

// 基类  
class Shape {  
public:  
    void setPosition(int x, int y) {  
        m_x = x;  
        m_y = y;  
    }  

protected:  
    int m_x, m_y; // 保护的成员变量  
};  

class Circle : public Shape {  
public:  
    void draw() {  
        std::cout << "Drawing a circle at (" << m_x << ", " << m_y << ")." << std::endl;  
    }  
};  

class Square : public Shape {  
public:  
    void draw() {  
        std::cout << "Drawing a square at (" << m_x << ", " << m_y << ")." << std::endl;  
    }  
};  

int main() {  
    Circle circle;  
    circle.setPosition(10, 20);  
    circle.draw(); // 输出: Drawing a circle at (10, 20).  

    Square square;  
    square.setPosition(30, 40);  
    square.draw(); // 输出: Drawing a square at (30, 40).  

    return 0;  
}
  • 输出
Drawing a circle at (10, 20).  
Drawing a square at (30, 40).

代码解析

  • 基类定义
class Shape {  
public:  
    void setPosition(int x, int y) {  
        m_x = x;  
        m_y = y;  
    }  

protected:  
    int m_x, m_y; // 保护的成员变量  
};
  • 基类 Shape 是一个描述形状的类。

  • setPosition(int x, int y) 方法 是一个公共方法,用于设置形状的坐标。通过设置 m_x 和 m_y 成员变量,基类提供了一个基本的行为。

  • protected 修饰符 表示 m_x 和 m_y 只能在 Shape 类及其派生类中访问。这赋予了派生类对这些成员的访问权限,允许它们读取和修改位置。

  • 派生类定义

class Circle : public Shape {  
public:  
    void draw() {  
        std::cout << "Drawing a circle at (" << m_x << ", " << m_y << ")." << std::endl;  
    }  
};
  • class Circle : public Shape 表示 Circle 类派生自 Shape 类,继承了所有公共和保护成员。
  • void draw() 方法 在 Circle 类中实现,负责输出当前圆形的坐标信息。
class Square : public Shape {  
public:  
    void draw() {  
        std::cout << "Drawing a square at (" << m_x << ", " << m_y << ")." << std::endl;  
    }  
};
  • class Square : public Shape 类似于 Circle 类,Square 也继承自 Shape 类。

  • draw() 方法负责输出当前正方形的坐标信息。

  • 主函数实现

int main() {  
    Circle circle;  
    circle.setPosition(10, 20);  
    circle.draw(); // 输出: Drawing a circle at (10, 20).  

    Square square;  
    square.setPosition(30, 40);  
    square.draw(); // 输出: Drawing a square at (30, 40).  

    return 0;  
}
  • 在 main() 函数中,首先创建了一个 Circle 对象。
  • circle.setPosition(10, 20); 调用基类的 setPosition 方法设置圆形的坐标。
  • circle.draw(); 调用 Circle 类的 draw() 方法输出圆形的坐标。
  • 类似的,对 Square 类进行相同的操作,设置其位置并输出。

关键点

  • Shape 类提供了一些具体的实现,比如 setPosition() 方法来设置形状的位置。
  • Circle 和 Square 类继承自 Shape,它们可以使用 Shape 中已实现的方法。
  • 子类可以重用父类的代码,也可以扩展其功能。

总结

  • 实现继承的作用:

    • Circle 和 Square 类继承自 Shape 类,能够重用基类的代码,特别是 setPosition 方法,避免了重复实现。
    • 通过继承,所有形状类可以共享 Shape 类的成员和方法,简化代码结构。
  • 访问控制:

    • 使用 protected 修饰符允许派生类访问基类的成员变量,保持数据的封装性,但同时允许派生类访问必要的数据。
  • 良好的可扩展性:

    • 由于 Shape 类作为基类,可以轻松添加更多形状(例如 Triangle),只需继承 Shape 类并实现 draw() 方法而不需要重复代码。
  • 多态性:

    • 在这个简单的实现中没有用到多态性,但可以通过把 draw() 方法声明为虚函数和使用基类指针或引用来增强多态特性,这样可以通过基类类型处理不同的派生类对象。

区别

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WPF+MVVM案例实战与特效(三十八)- 封装一个自定义的数字滚动显示控件

文章目录 1、运行效果2、案例实现1、功能设计2、页面布局3、控件使用4、运行效果3、拓展:多数字自定义控件1、控件应用4、总结1、运行效果 在Windows Presentation Foundation (WPF)应用程序中,自定义控件允许开发者创建具有特定功能和外观的独特UI元素。本博客将介绍一个名…

2024年12月HarmonyOS应用开发者高级认证全新题库

注意事项&#xff1a;切记在考试之外的设备上打开题库进行搜索&#xff0c;防止切屏三次考试自动结束&#xff0c;题目是乱序&#xff0c;每次考试&#xff0c;选项的顺序都不同&#xff0c;作者已于2024年12月15日又更新了一波题库&#xff0c;题库正确率99%&#xff01; 新版…

【Java学习笔记】JUnit

一、为什么需要 JUnit 二、基本介绍 三、实现方法 第一次添加&#xff1a; 在需要测试的方法处输入 Test注解&#xff0c;快捷键AltInsert选择添加版本&#xff08;常用JUnit5.4&#xff09; 出现绿色箭头可进行测试和编译

MySQL误删除 binlog 还原 恢复已删除数据 实战 超详细

硬盘有价&#xff0c;数据无价&#xff0c;数据库执行&#xff0c;谨慎操作&#xff01; binlog日志还原不适用于直接删表删库的误操作&#xff01; 目录 实战恢复 1、导出相关时间binlog数据 2、找到对应语句以及pos区间 3、导出改动区间的sql 4、将binlog导出的sql转换…

百度地图JavaScript API核心功能指引

百度地图JavaScript API是一套由JavaScript语言编写的应用程序接口&#xff0c;它能够帮助您在网站中构建功能丰富、交互性强的地图应用&#xff0c;包含了构建地图基本功能的各种接口&#xff0c;提供了诸如本地搜索、路线规划等数据服务。百度地图JavaScript API支持HTTP和HT…

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(五)

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(五) 你好,我是拉依达。 感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动 相关内容搜索的推荐首位,感谢大家支持。 《拉…

C语言简单日志宏

最近调试C代码&#xff0c;发现要写很多打印的内容不是很方便&#xff0c;于是简单写一下C语言的日志来方便自己调试&#xff1a; 1. 简单打印带标识的日志信息 #include "stdio.h" #define PRINT(...) \do \{ …

【算法】—— 前缀和

一、区间求和问题 给定一个长度为n的序列a&#xff0c;有m次查询&#xff0c;每次查询输出一个连续区间的和。 使用暴力做法求解是将每次查询都遍历该区间求和 //暴力做法import java.util.Scanner;public class Test {public static void main(String[] args){Scanner scan…

详解下c语言下的多维数组和指针数组

在实际c语言编程中&#xff0c;三维及以上数组我们使用的很少&#xff0c;二维数组我们使用得较多。说到数组&#xff0c;又不得关联到指针&#xff0c;因为他们两者的联系太紧密了。今天我们就详细介绍下c语言下的多维数组(主要是介绍二维数组)和指针。 一、二维数组 1.1&am…

【实验】【H3CNE邓方鸣】交换机端口安全实验+2024.12.11

实验来源&#xff1a;邓方鸣交换机端口安全实验 软件下载&#xff1a; 华三虚拟实验室: 华三虚拟实验室下载 wireshark&#xff1a;wireshark SecureCRT v8.7 版本: CRT下载分享与破解 文章目录 dot1x 开启802.1X身份验证 开启802.1X身份验证&#xff0c;需要在系统视图和接口视…

leetcode-73.矩阵置零-day5

class Solution {public void setZeroes(int[][] mat) {int m mat.length, n mat[0].length;// 1. 扫描「首行」和「首列」记录「首行」和「首列」是否该被置零boolean r0 false, c0 false;for (int i 0; i < m; i) {if (mat[i][0] 0) {r0 true;break;}}for (int j …

C++ webrtc开发(非原生开发,linux上使用libdatachannel库)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、libdatachannel库的下载和build二、开始使用 1.2.引入库3.开始使用 总结 前言 使用c开发webrtc在互联网上留下的资料甚少&#xff0c;经过我一段时间的探…

windows11 专业版 docker desktop 安装指南

家庭中文版需升级专业版&#xff0c;家庭版没有hyper-v。 开始运行optionalfeatures.exe打开windows功能 安装wsl2 步骤 1 - 启用适用于 Linux 的 Windows 子系统步骤 2 - 检查运行 WSL 2 的要求步骤 3 - 启用虚拟机功能步骤 4 - 下载 Linux 内核更新包 步骤 1 - 启用适用于 L…

解锁前端开发速度的秘密武器【Vite】

在前端开发的江湖中&#xff0c;有人偏爱 Webpack 的强大与稳定&#xff0c;有人钟情于 Rollup 的轻量与高效。而 Vite&#xff0c;这个后来居上的工具&#xff0c;却以“极致的快”和“极简的易”赢得了开发者的芳心。众所周知万事都有缘由&#xff0c;接下来我们就来深度剖析…

AI发展与LabVIEW程序员就业

人工智能&#xff08;AI&#xff09;技术的快速发展确实对许多行业带来了变革&#xff0c;包括自动化、数据分析、软件开发等领域。对于LabVIEW程序员来说&#xff0c;AI的崛起确实引发了一个值得关注的问题&#xff1a;AI会不会取代他们的工作&#xff0c;导致大量失业&#x…

决策曲线分析(DCA)中平均净收益用于评价模型算法(R自定义函数)

决策曲线分析&#xff08;DCA&#xff09;中平均净收益用于评价模型算法 DCA分析虽然不强调用来评价模型算法或者变量组合的优劣&#xff0c;但是实际应用过程中感觉DCA曲线的走势和模型的效能具有良好的一致性&#xff0c;其实这种一致性也可以找到内在的联系&#xff0c;比如…

【Linux】:多线程(POSIX 信号量 、基于环形队列的生产消费者模型)

&#x1f4c3;个人主页&#xff1a;island1314 ​​ &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 目录 1. POSIX 信号量…

人工智能的历史概况和脉络

人工智能( AI ) 的历史始于古代&#xff0c;当时有神话、故事和谣言称&#xff0c;人工生物被工匠大师赋予了智慧或意识。从古代到现在&#xff0c;对逻辑和形式推理的研究直接导致了20 世纪 40 年代可编程数字计算机的发明&#xff0c;这是一种基于抽象数学推理的机器。这种设…

昇思25天学习打卡营第33天|共赴算力时代

文章目录 一、平台简介二、深度学习模型2.1 处理数据集2.2 模型训练2.3 加载模型 三、共赴算力时代 一、平台简介 昇思大模型平台&#xff0c;就像是AI学习者和开发者的超级基地&#xff0c;这里不仅提供丰富的项目、模型和大模型体验&#xff0c;还有一大堆经典数据集任你挑。…

基于32单片机的RS485综合土壤传感器检测土壤PH、氮磷钾的使用(超详细)

1-3为RS485综合土壤传感器的基本内容 4-5为基于STM32F103C8T6单片机使用RS485传感器检测土壤PH、氮磷钾并显示在OLED显示屏的相关配置内容 注意&#xff1a;本篇文件讲解使用的是PH、氮磷钾四合一RS485综合土壤传感器&#xff0c;但里面的讲解内容适配市面上的所有多合一的RS…