C++三大特性之一:多态

news2024/12/25 9:17:56

一、多态

1、通过指针创建对象(动态分配)

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class show" << endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出:Derived class show
    delete b;
    return 0;
}

在这个程序中,Base 类指针 b 被分配为指向一个 Derived 类对象。因为 show 是一个虚函数,并且 b 实际上指向的是一个 Derived 类对象,所以当调用 b->show() 时,会调用 Derived 类中的 show 方法,输出 "Derived class show"。

2.直接创建对象(静态分配)

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class show" << endl;
    }
};

int main() {
    Base b;
    b.show(); // 输出:Base class show
    return 0;
}

在这个程序中,Base 类对象 b 是静态分配的。虽然 Base 类有一个虚函数 show,但是 bBase 类的对象,而不是 Derived 类的对象。因此,当调用 b.show() 时,会调用 Base 类中的 show 方法,输出 "Base class show"。 

2.1静态分配具体怎么实现的

在C++中,静态分配意味着对象的内存分配在编译时就已经确定了。这种分配方式通常在栈上进行,当函数执行结束时,这些对象的内存会自动释放。以下是静态分配的详细解释和实现方式:

静态分配的实现

  1. 声明对象:在函数内部或类的成员变量中直接声明对象。这些对象在声明时就分配了内存,并在作用域结束时自动释放。

  2. 作用域管理:静态分配的对象在它们声明的作用域内是有效的。一旦超出作用域,对象的析构函数(如果有)将自动调用,并释放内存。

  3. 示例代码

    以下是一个使用静态分配的示例代码:

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() {
        cout << "Base destructor" << endl;
    }
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

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

int main() {
    Base b;
    b.show(); // 输出:Base class show
    return 0;
}

对象声明

  1. Base b;
  2. 这行代码在栈上分配一个 Base 类对象 b

  3. 构造函数调用

    • 当对象 b 被声明时,Base 类的构造函数被调用,输出 "Base constructor"。
  4. 虚函数调用

    • 当调用 b.show() 时,因为 bBase 类的对象,而不是 Derived 类的对象,所以调用的是 Base 类的 show 方法,输出 "Base class show"。
  5. 析构函数调用

    • main 函数结束时,b 超出其作用域,Base 类的析构函数被调用,输出 "Base destructor"。

静态分配的特点

  • 内存管理简单:静态分配的对象在作用域结束时自动释放,不需要显式删除。
  • 生命周期受作用域控制:对象的生命周期由它们声明的作用域决定。
  • 栈上分配:静态分配通常在栈上进行,这意味着对象的分配和释放速度很快,但受限于栈的大小。

与动态分配的对比

动态分配则在堆上进行,需要显式的内存管理(例如 newdelete)。它允许在运行时决定对象的生存周期,但也增加了内存泄漏的风险。如果没有正确释放动态分配的内存,程序会导致内存泄漏。

int main() { Base* b = new Derived(); b->show(); // 输出:Derived class show delete b; // 手动释放内存,调用 Derived 和 Base 的析构函数 return 0; }

在上面的动态分配示例中,需要手动释放通过 new 分配的内存(delete b),否则会导致内存泄漏。

 2.2调用构造函数是静态分配吗?

     调用构造函数本身不是静态分配,但构造函数的调用与对象的创建方式有关。静态分配和动态分配分别决定了构造函数在何时以及如何被调用。让我们更详细地探讨静态分配的过程。

静态分配的详细过程

静态分配指的是在编译时确定内存分配,并在栈上分配内存。当在函数内部声明对象时,会自动调用其构造函数进行初始化。静态分配的对象在作用域结束时会自动调用析构函数释放内存。

  • 静态分配:在栈上分配,编译时确定,作用域结束时自动释放。
  • 动态分配:在堆上分配,运行时确定,需要手动释放。

构造函数的调用与对象的创建方式(静态或动态分配)相关,在对象创建时自动调用,无论是静态分配还是动态分配。

2.3对象的创建时,构造函数就自动执行吗?

是的,在C++中,当对象被创建时,构造函数会自动执行以初始化该对象。无论是静态分配还是动态分配,构造函数都会在对象创建时被调用。

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() {
        cout << "Base destructor" << endl;
    }
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

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

int main() {
    Base* b = new Derived(); // 动态分配,b 指向 Derived 对象
    b->show(); // 输出:Derived class show
    delete b;  // 释放内存,调用 Derived 和 Base 的析构函数
    return 0;
}

运行结果: 

Base constructor
Derived constructor
Derived class show
Derived destructor
Base destructor

解释

  1. 动态分配对象

    Base* b = new Derived();
    

    这行代码在堆上分配了一个 Derived 类对象,并将 b 指针指向该对象。在 new Derived() 执行时,首先调用 Base 类的构造函数,然后调用 Derived 类的构造函数,依次输出 "Base constructor" 和 "Derived constructor"。

  2. 调用成员函数

    调用 b->show() 时,因为 b 实际上指向一个 Derived 类对象,并且 show 是一个虚函数,所以调用 Derived 类的 show 方法,输出 "Derived class show"。
  3. 释放内存

    delete b;

    这行代码释放 b 指向的内存,首先调用 Derived 类的析构函数,然后调用 Base 类的析构函数,依次输出 "Derived destructor" 和 "Base destructor"。

总结

  • 静态分配:对象在栈上分配,声明时自动调用构造函数,作用域结束时自动调用析构函数。
  • 动态分配:对象在堆上分配,new 操作符执行时自动调用构造函数,delete 操作符执行时自动调用析构函数。

在对象创建时,构造函数会自动执行,无论是静态分配还是动态分配。这是对象初始化过程的一个重要部分,确保对象在使用前被正确初始化。

2.4构造函数在项目中的具体运用

1. 初始化成员变量

在对象创建时通过构造函数初始化成员变量,以确保对象在使用前处于有效状态。

2. 资源分配和管理

构造函数可以用于分配资源,例如打开文件、分配动态内存、建立数据库连接等。析构函数用于释放这些资源,防止内存泄漏和资源泄漏。

3. 依赖注入

构造函数可以接受参数,用于注入对象依赖的其他对象。这样可以提高代码的灵活性和可测试性。

4. 单例模式

构造函数可以用于实现设计模式,例如单例模式,确保一个类只有一个实例,并提供一个全局访问点。

2.5纯虚函数和虚函数的区别

(1)虚函数的使用

#include <iostream>
using namespace std;

// 抽象基类
class Shape {
public:
    virtual void draw() {};
    virtual double area() { return 0; };
    virtual ~Shape() {} // 虚析构函数
};


class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    void draw()  {
        cout << "Drawing Circle" << endl;
    }
    double area()  {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {
        cout << "构造函数的实现" << endl;
    }
    void draw() {
        cout << "Drawing Rectangle" << endl;
    }
    double area()  {
        return width * height;
    }
};


int main() {
    Shape* shapes[2];
    shapes[0] = new Circle(5.0);
    shapes[1] = new Rectangle(4.0, 6.0);

    for (int i = 0; i < 2; ++i) {
        shapes[i]->draw();
        cout << "Area: " << shapes[i]->area() << endl;
        delete shapes[i]; // 记得删除动态分配的对象
    }

    return 0;
}


运行结果:

(2)纯虚函数的使用

#include <iostream>
using namespace std;

// 抽象基类
class Shape {
public:
    // 纯虚函数,必须在派生类中实现
    virtual void draw() const = 0;
    virtual double area() const = 0;
    virtual ~Shape() { cout << "基类析构函数的实现" << endl; } // 虚析构函数
};


class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) { cout << "Circle构造函数的实现" << endl; }
    void draw() const override {
        cout << "Drawing Circle" << endl;
    }
    double area() const override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {
    
    }
    void draw() const override {
        cout << "Drawing Rectangle" << endl;
    }
    double area() const override {
        return width * height;
    }
};


int main() {
    Shape* shapes[2];
    shapes[0] = new Circle(5.0);
    shapes[1] = new Rectangle(4.0, 6.0);

    for (int i = 0; i < 2; ++i) {
        shapes[i]->draw();
        cout << "Area: " << shapes[i]->area() << endl;
        delete shapes[i]; // 记得删除动态分配的对象
    }

    return 0;
}


运行结果: 

区别

  1. 定义方式

    • 虚函数:virtual void show() { ... }
    • 纯虚函数:virtual void show() = 0;
  2. 实现

    • 虚函数:可以在基类中提供实现,也可以在派生类中重写。
    • 纯虚函数:不能在基类中提供实现,必须在派生类中实现。
  3. 实例化

    • 虚函数:基类可以实例化。
    • 纯虚函数:包含纯虚函数的类是抽象类,不能实例化。
  4. 用途

    • 虚函数:用于实现多态性。
    • 纯虚函数:用于定义接口,强制派生类实现特定的函数。
什么时候使用虚函数?
  • 多态行为:当你希望基类提供一个默认的行为,但允许派生类重写时,使用虚函数。
  • 可选重写:当派生类不一定需要提供自己的实现时,可以在基类中提供一个虚函数的默认实现。
什么时候使用纯虚函数?
  • 抽象接口:当你希望基类只定义接口,而不提供任何实现时,使用纯虚函数。
  • 强制实现:当你希望所有派生类都必须提供自己的实现时,使用纯虚函数。

2.6虚函数映射表

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base show" << endl;
    }
    virtual void display() {
        cout << "Base display" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived show" << endl;
    }
    void display() override {
        cout << "Derived display" << endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出:Derived show
    b->display(); // 输出:Derived display
    delete b;
    return 0;
}

Base* b = new Derived(); 

   当创建 Derived 对象时,编译器会为 Derived 类生成一个虚函数表,并在对象中存储一个指向该表的指针。虚函数表包含 Derived 类中虚函数 showdisplay 的地址。

多态调用
  • b->show()b->display() 是通过基类指针 b 调用的。
  • 程序会通过 b 的 V-Table Pointer 查找 Derived 类的虚函数表,然后从表中找到 showdisplay 函数的地址并调用它们。
注意事项
  • V-Table 的生成:虚函数表是在编译时生成的,每个类只有一张虚函数表。
  • 对象的 V-Table Pointer:每个对象包含一个指向相应虚函数表的指针(隐藏在对象中),该指针在对象构造时被初始化。
  • 内存开销:使用虚函数表会增加一些内存开销(存储虚函数表指针和虚函数表本身),并稍微增加函数调用的时间开销(间接调用)。

2.7为什么要使用虚析构函数

(1)通常情况的析构函数

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() { // 虚析构函数
        cout << "Base destructor" << endl;
    }
};

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

int main() {
    Base* b = new Derived();
    delete b; // 正确调用派生类的析构函数
    return 0;
}

可以看到释放资源的时候,delete b只是调用了基类的析构函数,并没有对派生类进行内存释放。

(2)使用虚函数的析构函数

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
   virtual ~Base() { // 虚析构函数
        cout << "Base destructor" << endl;
    }
};

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

int main() {
    Base* b = new Derived();
    delete b; // 正确调用派生类的析构函数
    return 0;
}

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

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

相关文章

Android 13 修改系统导航默认值

Android 13 原生系统上&#xff0c;设置-系统-手势-系统导航 菜单&#xff0c;可以修改系统导航方式。 手势导航&#xff1a; 三按钮导航&#xff1a; adb 获取当前导航方式&#xff0c;手势导航 是 2 &#xff0c;三按钮导航是 0 。 settings get secure navigation_mode 修…

游戏AI的创造思路-技术基础-深度学习(2)

感觉坑越挖越大&#xff0c;慢慢填~~~~ 继续上篇进行填坑&#xff0c;这一篇我们介绍下循环神经网络 目录 3.2. 循环神经网络&#xff08;RNN&#xff09; 3.2.1. 算法形成过程 3.2.2. 运行原理 3.2.3. RNN有哪些优缺点 3.2.4. RNN参数 3.2.5. 如何选择RNN模型参数 3.2…

odoo17 tree视图添加按钮

需求描述 点击下图中tree视图上的同步退货单按钮&#xff0c;弹出相应的form视图进行退货单同步&#xff0c;然后点击同步按钮调用后端python代码处理。 实现步骤 主要文件目录结构 js文件的创建 /** odoo-module **/ import { registry } from "web/core/registry&quo…

芒果YOLOv10改进66:特征融合Neck篇之原创 HFAMPAN 结构:信息高阶特征对齐融合和注入,全局融合多级特征,将全局信息注入更高级别

💡本篇内容:YOLOv10 改进原创 HFAMPAN 结构,信息高阶特征对齐融合和注入,全局融合多级特征,将全局信息注入更高级别 💡🚀🚀🚀本博客 改进源代码改进 适用于 YOLOv10 按步骤操作运行改进后的代码即可 💡本文提出改进 原创 方式:二次创新,YOLOv10 专属 论文…

Spring Boot基础入门

引言 Spring Boot是一个开源的Java框架&#xff0c;旨在简化Spring应用程序的创建和部署过程。它提供了一种快速和简便的方式来创建独立的、生产级别的基于Spring的应用程序。本文将介绍Spring Boot的基础知识&#xff0c;包括其核心特性、如何开始使用Spring Boot以及构建你的…

Docker 安装和加速

目录 1.安装 2.了解 docker 信息 3.查询状态 4. 重新启动Docker 1.安装 yum install –y docker 2.了解 docker 信息 cat /etc/redhat-release 3.查询状态 systemctl status docker 4.支持 1.12 的 docker 镜像加速 sudo mkdir -p /etc/docker sudo tee /etc/docke…

关于关闭防火墙后docker启动不了容器

做项目的时候遇到个怪事&#xff0c;在Java客户端没办法操作redis集群。反复检查了是否运行&#xff0c;端口等一系列细节的操作&#xff0c;结果都不行。 根据提示可能是Linux的防火墙原因。于是去linux关闭了防火墙。 关闭后果不其然 可以操作reids了&#xff0c;可是没想到另…

【linux学习十七】文件服务管理

一、FTP FTP server:FTP(File Transfer Protocol,文件传输协议 )是 TCP/IP 协议组中的协议之一 软件包&#xff1a;vsftpd/安装 yum -y install vsftpd//准备文件 touch /var/ftp/abc.txt //注释:FTP服务器的主目录:“/var/ftp/”&#xff0c;是FTP程序分享内容的本机目录…

ArkUI开发学习随机——得物卡片,京东登录界面

案例一&#xff1a;得物卡片 代码&#xff1a; Column(){Column(){Image($r("app.media.mihoyo")).width(200).height(200)Row(){Text("今晚玩这个 | 每日游戏打卡").fontWeight(700).fontSize(16).padding(4)}.width(200)Text("No.12").fontWe…

罗德与施瓦茨 ZND (RS) ZNB8 矢量网络分析仪

R&SZND 矢量网络分析仪 简介 R&S ZND 是一款提供最高 4.5 GHz 的单向测量的基本网络分析仪。 用户可选择选件以执行双向测量&#xff0c;并将频率范围扩展到 8.5 GHz。 主要特点 双端口网络分析仪&#xff0c;适用于 100 kHz 至 4.5 GHz 的单向测量 频率范围可扩展…

一分钟扫盲:互联网产品B端,C端,G端都是啥,有什么特征。

在互联网产品中&#xff0c;B端、C端和G端是常用的分类方式&#xff0c;用于描述产品的目标用户群体和市场定位。 B端&#xff08;Business-to-Business&#xff09;&#xff1a; B端产品是面向企业或机构的产品&#xff0c;即企业与企业之间的交互和服务。B端产品的特点包括…

​Python20 Numpy基础

NumPy&#xff08;Numerical Python&#xff09;是一个开源的Python库&#xff0c;广泛用于科学计算。它提供了一个高性能的多维数组对象&#xff0c;以及用于处理这些数组的工具和函数。NumPy是数据分析、机器学习、工程和科学研究中不可或缺的工具之一&#xff0c;因为它提供…

Navicat连接Oracle出现Oracle library is not loaded的解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 使用Navicat链接Oracle的时候,出现如下提示:Oracle library is not loaded. 截图如下所示: 2. 原理分析 通常是由于缺少必需的 Oracle 客户端库或环境变量未正确配置所致 还有一种情况是 32位与64位的不匹配:Navica…

Labview_网络流

网络流的介绍 网络流是一种易于配置、紧密集成的动态通信方法&#xff0c;用于将数据从一个应用程序传输到另一个应用程序&#xff0c;其吞吐量和延迟特性可与 TCP 相媲美。但是&#xff0c;与 TCP 不同的是&#xff0c;网络流直接支持任意数据类型的传输&#xff0c;而无需先…

一键设置水印,全面护航:跨境平台产品刊登新功能上线

一、Allegro、OZON、Coupang、Cdiscount、Wish、Temu、Walmart、OnBuy、TikTok、Wildberries平台新增产品刊登支持设置水印。 【普通水印】直接跳转到添加水印页面&#xff0c;勾选所要的图片&#xff0c;点击确定&#xff0c;自动替换原图。 【管理水印】直接跳转到水印模版页…

JAVA【案例5-2】模拟默认密码自动生成

【模拟默认密码自动生成】 1、案例描述 本案例要求编写一个程序&#xff0c;模拟默认密码的自动生成策略&#xff0c;手动输入用户名&#xff0c;根据用户名自动生成默认密码。在生成密码时&#xff0c;将用户名反转即为默认的密码。 2、案例目的 &#xff08;1&#xff09…

汇凯金业:现货黄金交易规则有哪些 投资必读

现货黄金投资近年来发展迅猛&#xff0c;吸引了许多新手投资者进入市场。那么&#xff0c;现货黄金交易规则究竟有哪些?本文将详细介绍现货黄金交易的关键规则&#xff0c;帮助投资者更好地掌握投资技巧&#xff0c;实现稳健收益。 一、保证金交易制 什么是保证金交易? 现…

【智能家居照明磁吸灯驱动方案】LED磁吸轨道灯智能照明调光控制芯片FP7195,耐压80V降压恒流无频闪芯片,0.1%深度调光无频闪无抖动,调光细腻丝滑

随着极简主义无主灯照明的设计风潮来袭&#xff0c;磁吸灯成了室内灯光设计的重点元素&#xff0c;磁吸灯的设计采用模块化的概念。 预设好轨道后&#xff0c;可以自由选择光源的样式&#xff1a;筒灯、射灯、格栅灯、泛光条、吊线灯等等&#xff0c;各种灯具及不同色温光源的…

怎么改png图片的颜色?

要改变PNG图片的颜色&#xff0c;可以采取以下几种方法&#xff1a; 使用Photoshop等图像编辑软件&#xff1a;或者&#xff0c;也可以使用Photoshop中的选区工具&#xff08;如矩形选框、椭圆选框、套索工具等&#xff09;选中图片中需要改变颜色的部分&#xff0c;然后创建一…

Kompas AI自然语言处理能力对比

一、引言 自然语言处理&#xff08;NLP&#xff09;是衡量人工智能&#xff08;AI&#xff09;系统智能程度的重要指标之一。NLP技术的进步使得机器能够理解、解释和生成人类语言&#xff0c;在各个领域中发挥了巨大的作用。本文将对比Kompas AI与其他主要AI产品在NLP方面的表…