C++零基础教程(抽象类和接口)

2023-07-20 00:00:00


前言

本篇文章来讲解抽象类和接口的概念,抽象类和接口都需要依靠我们之前讲解的虚函数来实现,那么我们就来看看如何使用虚函数来实现抽象类和接口吧。

一、抽象类概念

抽象类是一种不能直接实例化(即创建对象)的类,它被用作其他类的基类或接口。抽象类通过声明纯虚函数(没有具体实现的虚函数)来定义接口,而派生类必须实现这些纯虚函数才能被实例化。
以下是抽象类的特点和用途:

1.抽象类不能直接实例化:抽象类只能作为基类用于继承,并为派生类提供接口和共享的行为。由于抽象类中存在纯虚函数,没有为其提供具体实现,因此不能直接创建抽象类的对象。

2.包含纯虚函数:抽象类中至少包含一个纯虚函数,通过在函数声明的末尾加上= 0来表示纯虚函数。纯虚函数没有具体的实现,派生类必须提供实现才能实例化。当一个类中有纯虚函数时,该类就成为抽象类。

3.定义接口:抽象类定义了一组纯虚函数,用于声明派生类必须实现的操作。这些纯虚函数可以视为接口,规定了派生类的行为。

4.提供默认实现:抽象类中可以包含非纯虚函数,它们可以有默认实现。这些非纯虚函数提供了通用行为或共享功能,派生类可以选择性地重写这些函数。

5.强迫派生类实现纯虚函数:派生类必须实现基类中的纯虚函数,否则派生类也会被视为抽象类,无法实例化。

6.支持多态性:通过继承抽象类并实现纯虚函数,派生类可以对基类的接口进行自定义实现,并以多态的方式使用这些派生类对象。

抽象类提供了一种定义接口和约束派生类的机制,它是面向对象编程中的重要概念。通过使用抽象类,我们可以设计出具有共享接口的相似对象,并实现代码重用和扩展性。同时,抽象类也允许我们使用多态性来处理不同类型的对象,提高代码的灵活性和可维护性。

二、抽象类例子

当深入OOP(面向对象编程)时,抽象类的一个常见用例是定义一个通用的形状类,并通过派生类实现具体的形状(如矩形、圆形等)。下面是一个用C++实现的示例:

#include <iostream>

// 抽象类 Shape
class Shape {
public:
    virtual double getArea() const = 0; // 纯虚函数
    virtual double getPerimeter() const = 0; // 纯虚函数
    virtual void print() const = 0; // 纯虚函数
};

// 派生类 Rectangle
class Rectangle : public Shape {
private:
    double length;
    double width;

public:
    Rectangle(double l, double w) : length(l), width(w) {}

    double getArea() const override {
        return length * width;
    }

    double getPerimeter() const override {
        return 2 * (length + width);
    }

    void print() const override {
        std::cout << "Rectangle: length = " << length << ", width = " << width << std::endl;
    }
};

// 派生类 Circle
class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double getArea() const override {
        return 3.14 * radius * radius;
    }

    double getPerimeter() const override {
        return 2 * 3.14 * radius;
    }

    void print() const override {
        std::cout << "Circle: radius = " << radius << std::endl;
    }
};

int main() {
    Rectangle rectangle(5, 3);
    Circle circle(2.5);

    Shape* shape1 = &rectangle; // 使用基类指针指向派生类对象
    Shape* shape2 = &circle;

    shape1->print(); // 多态调用
    std::cout << "Area: " << shape1->getArea() << ", Perimeter: " << shape1->getPerimeter() << std::endl;

    shape2->print();
    std::cout << "Area: " << shape2->getArea() << ", Perimeter: " << shape2->getPerimeter() << std::endl;

    return 0;
}

在上述代码中,定义了一个抽象类Shape,并声明了纯虚函数getArea()、getPerimeter()和print(),用于获取形状的面积、周长和打印形状信息。然后通过派生类Rectangle和Circle分别实现这些纯虚函数。

在main()函数中,创建了具体的Rectangle和Circle对象,并使用基类指针Shape* 指向派生类对象。通过多态性,我们可以调用基类指针的函数来实现对派生类对象的操作。最后,使用多态特性打印并计算了各个形状的面积和周长。

三、接口概念

在面向对象编程中,接口是一种定义类行为的抽象规范。它定义了类应该具有的方法和属性,但没有提供具体的实现。接口只关注类提供的操作,而不关心其内部实现细节。

以下是接口的特点和用途:

1.纯虚函数:接口中的方法通常是纯虚函数(C++中的纯虚函数通过= 0表示),即没有具体实现的函数。接口声明了类必须实现的操作,但不指定具体的实现方式。

2.定义行为规范:接口定义了一组方法,并规范了类应该具有的行为。它描述了一个类的功能,提供了对类的使用者的一种合约或承诺。

3.多态性支持:通过实现接口,类可以具备多态性。多态允许使用基类指针或引用来引用派生类的对象,并在运行时调用相应的方法。

4.实现解耦:接口可以将类的实现与其使用者分离,使得类和使用者之间只通过接口定义的方法进行通信。这样可以降低代码的耦合性,使系统更加灵活和可扩展。

5.重用和扩展:接口允许类实现多个接口,从而在不同的上下文中使用相同的类。这样可以实现代码的重用并支持类的扩展。

6.设计契约:接口可以被视为类与使用者之间的契约或协议。通过接口,类向外部世界公开一组操作,使用者通过接口来与类进行交互,而不需要关注其内部实现。

四、接口和抽象类的区别

接口和抽象类是面向对象编程中的两个重要概念,它们在某些方面有相似之处,但也存在一些关键区别。下面是接口和抽象类之间的主要区别:

1.实现方式:接口(Interface)只能定义方法和常量,不能包含具体的实现代码,而抽象类(Abstract Class)可以包含方法的实现代码和属性等成员。

2.继承关系:类通过实现接口来遵循接口定义的行为规范,一个类可以实现多个接口。而抽象类通过继承来扩展或修改其基类的行为。

3.多重继承限制:一个类可以实现多个接口,因此可以具备不同接口的特性。然而,类只能继承一个抽象类,因为大多数编程语言不支持多重继承。

4.构造函数:抽象类可以拥有构造函数,而接口不能具有构造函数。抽象类的构造函数主要用于在实例化时执行一些通用逻辑。

5.默认实现:接口中的方法都是抽象的,没有默认实现。而抽象类可以包含抽象方法和非抽象方法,非抽象方法可以提供默认实现。

6.设计层次:接口用于定义行为规范,将类的实现与使用者解耦。抽象类更倾向于在类的继承层次结构中扮演基类的角色,提供一些通用的行为和属性供子类继承和扩展。

总结

本篇文章就讲解到这里,下篇文章我们继续讲解。

更多推荐

js中哪些地方会用到window?

前言Window对象是JavaScript中的顶层对象,它代表了浏览器中打开的窗口或者标签页。浏览器中打开的每一个窗口/标签页都会有一个对应的Window对象。在浏览器中,全局作用域的this就是指向Window对象。正文在JavaScript中,window对象表示浏览器窗口(通常也称为浏览器窗口或浏览器窗口)。以下

Linux 中的make/makefile

一:背景make是一个命令工具,是一个解释makefifile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,VisualC++的nmake,Linux下GNU的make。可见,makefifile都成为了一种在工程方面的编译方法。一个工程中的源文件不计数,其按类型、功能、模块分别

Redis常用应用场景

Redis是一款开源的基于内存的键值存储系统,它提供了多种数据结构和丰富的功能,适用于各种不同的应用场景。以下是Redis常用的应用场景:1.缓存:Redis最常见的用途就是作为缓存。由于Redis存储在内存中,读取速度非常快,可以显著减轻数据库的负载。将频繁读取的数据存储在Redis中,可以大幅提高应用的响应速度。2

SpringBoot-RabbitMQ

RabbitMQ是一个开源的消息中间件,它实现了AMQP(AdvancedMessageQueuingProtocol)协议,并提供了可靠的消息传递机制。SpringBoot中使用RabbitMQ实现异步消息的发送和接收。使用SpringBoot提供的AmqpTemplate和@RabbitListener注解进行消息

数据分享|R语言逻辑回归、Naive Bayes贝叶斯、决策树、随机森林算法预测心脏病...

全文链接:http://tecdat.cn/?p=23061这个数据集(查看文末了解数据免费获取方式)可以追溯到1988年,由四个数据库组成。克利夫兰、匈牙利、瑞士和长滩。"目标"字段是指病人是否有心脏病。它的数值为整数,0=无病,1=有病(点击文末“阅读原文”获取完整代码数据)。数据集信息:目标:主要目的是预测给定的

难得有个冷静的程序员发言了:纯编码开发实施的项目,失败的案例也有很多

难得有个冷静的程序员发言了:纯编码开发实施的项目,失败的案例也有很多。假如用低代码实施,能达到不失败或提高成功率,对软件开发项目交付,会是重大的价值。我的观点:两者都可能失败,不同的是,传统编程开发的项目,失败都是发生在项目的中后期,前期靠PPT保证,在实施过程中,做着做着就发现做不下去了(需求风险,成本风险,技术风险

硬件故障诊断:快速定位问题

🌷🍁博主猫头虎(🐅🐾)带您GotoNewWorld✨🍁🦄博客首页——🐅🐾猫头虎的博客🎐🐳《面试题大全专栏》🦕文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》🐾学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》🐅学会Gol

Golang数组和slice

Golang数组和Slice(切片)Go语言中数组长度固定不可变动,slice则可以增长缩短(使用较多)一、数组类型Go语言中数组长度固定,索引从0开始计数。需要注意数组的长度一开始必须是固定的,且不同长度的数组其表示不同的数据类型,相同的数组可以进行‘==’比较。数组作为函数参数是使用的是形参的方式,函数内不可改变其

Tokenview X-ray功能:深入探索EVM系列浏览器的全新视角

Tokenview作为一家领先的多链区块浏览器,为了进一步优化区块链用户的使用体验,我们推出了X-ray(余额透视)功能。该功能将帮助您深入了解EVM系列浏览器上每个地址的交易过程,以一种直观、简洁的方式呈现地址的进出账情况,让您轻松掌握资产流转。X-ray功能特性1多级合约调用的直观展示在区块链世界中,资产流转是一个

第七章(1):深度神经网络的基本结构、训练方法和在机器学习中的重要性

第七章(1):深度神经网络的基本结构、训练方法和在机器学习中的重要性作者:安静到无声个人主页作者简介:人工智能和硬件设计博士生、CSDN与阿里云开发者博客专家,多项比赛获奖者,发表SCI论文多篇。Thanks♪(・ω・)ノ如果觉得文章不错或能帮助到你学习,可以点赞👍收藏📁评论📒+关注哦!o( ̄▽ ̄)d欢迎大家来到

GDAL库学习

GDAL库学习GDAL是一个操作栅格数据和矢量数据的库,对图像而言,可以进行包括读取、写入、转换、处理各种操作。文章目录GDAL库学习RasterIO()函数1.添加引用2.读取图像3.获取图像基本信息4.保存输出图像5.释放RasterIO()函数RasterIO(GDALRWFlageRWFlag,intnXOff

热文推荐