学习c++的第十二天

2023-11-05 13:04
文章标签 c++ 学习 第十二天

本文主要是介绍学习c++的第十二天,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

多态性

多态性

虚函数

虚析构函数

纯虚函数


多态性

多态性

多态性的实现依赖于继承和虚函数。在面向对象编程中,通过基类的指针或引用来调用派生类的成员函数时,可以根据实际的对象类型决定调用哪个类的成员函数,从而实现多态性。

  • 在静态联编中,编译器在编译阶段就能确定调用的具体函数,这种情况下多态性的效果并不明显。而在动态联编中,编译器只能确定调用的是基类的成员函数,具体调用哪个派生类的函数则在运行时决定,这就发挥了多态性的优势。
  • 动态联编和多态性的典型应用是通过虚函数实现的。虚函数是在基类中声明的带有 virtual 关键字的成员函数,它可以被派生类重写(Override)。当使用基类的指针或引用调用虚函数时,实际调用的是派生类中重写的函数。

下面,我们围绕静态联编,给大家举一个简单例子:

#include <iostream>class Shape {
public:void draw() {std::cout << "绘制一个形状" << std::endl;}
};class Circle : public Shape {
public:void draw() {std::cout << "绘制一个圆形" << std::endl;}
};class Rectangle : public Shape {
public:void draw() {std::cout << "绘制一个矩形" << std::endl;}
};int main() {Shape shape;Circle circle;Rectangle rectangle;// 基类指针指向派生类对象Shape* shapePtr1 = &circle;Shape* shapePtr2 = &rectangle;// 调用基类的成员函数,由于静态联编,在编译时就确定了调用的函数shape.draw();       // 绘制一个形状shapePtr1->draw();  // 绘制一个形状shapePtr2->draw();  // 绘制一个形状return 0;
}

在上述示例中,我们有一个基类 Shape 和两个派生类 Circle 和 Rectangle。基类和派生类都有一个名为 draw 的成员函数。

在 main 函数中,我们创建了一个基类对象 shape 和两个派生类对象 circle 和 rectangle。然后,我们将派生类对象的地址赋给基类指针 shapePtr1 和 shapePtr2。

在调用成员函数时,基类对象 shape 和派生类对象的指针 shapePtr1 和 shapePtr2 都调用了 draw 函数。由于静态联编,在编译时就确定了调用的函数,因此无论是通过基类对象还是派生类对象的指针调用,都会执行基类的 draw 函数,输出 "绘制一个形状"。

这个示例展示了静态联编的特点,即在编译时就确定了调用哪个函数,不会根据对象的实际类型来决定调用哪个函数。

通过多态性,我们可以以一种统一的方式处理不同类型的对象。这样做的好处是,我们可以将对象视为其基类类型,而不需要关注具体是哪个派生类的实例。这使得我们可以设计出更加灵活和可扩展的代码,同时提高了代码的可读性和可维护性。

总结来说,多态性是面向对象编程的重要特性,它通过继承和虚函数机制实现。它允许不同类型的对象对同一个消息做出不同的响应,提高了代码的灵活性和可扩展性。静态联编和动态联编是多态性的两种实现方式,静态联编由于编译时候就已经确定好怎么执行,因此执行起来效率高;而动态联编想必虽然慢一些,但优点是灵活。两者各有千秋,有各自不同的使用场景。

虚函数

虚函数是用于实现动态联编和多态性的一种机制。通过将父类中的成员函数声明为虚函数,可以在子类中重写该函数并根据对象的实际类型选择调用哪个函数,从而实现多态性。

在 C++ 中,将函数声明为虚函数需要在函数名前加上关键字 virtual。当一个函数被声明为虚函数时,它的子类中的同名函数也会自动成为虚函数。当使用一个指向子类对象的基类指针或引用调用虚函数时,程序会在运行时动态地确定调用哪个函数,这就是动态联编。

一般形式如下:

virtual 函数返回值 函数名(形参)
{函数体
}

需要注意的是:

  1. 虚函数不能是静态成员函数或友元函数:虚函数依赖于对象的实际类型来确定调用哪个函数,而静态成员函数和友元函数不属于特定的对象,因此无法实现动态多态性,所以它们不能被声明为虚函数。
  2. 内联函数不能是虚函数:内联函数是在编译时插入代码的,而虚函数的调用是在运行时动态决定的。即使虚函数在类的定义中被标记为 inline,编译器仍然会将其视为非内联函数,因为虚函数的调用需要在运行时根据对象的实际类型来确定。
  3. 构造函数不能是虚函数,析构函数可以是虚函数:构造函数在创建对象时被调用,并负责初始化对象的成员变量等工作。由于在调用构造函数时对象尚未完全构建,因此无法实现动态多态性,所以构造函数不能声明为虚函数。而析构函数在销毁对象时被调用,可以实现动态多态性,通常情况下我们将析构函数声明为虚函数,以确保在删除基类指针时正确调用派生类的析构函数,防止内存泄漏。

以下是一个简单的使用虚函数的示例:

#include <iostream>
using namespace std;class Shape {
public:virtual void draw() {cout << "绘制一个形状" << endl;}
};class Circle : public Shape {
public:void draw() {cout << "绘制一个圆形" << endl;}
};class Rectangle : public Shape {
public:void draw() {cout << "绘制一个矩形" << endl;}
};int main() {Shape shape;Circle circle;Rectangle rectangle;Shape* shapePtr1 = &circle;Shape* shapePtr2 = &rectangle;// 通过基类指针调用虚函数,程序会在运行时动态地确定调用哪个函数shape.draw();       // 绘制一个形状shapePtr1->draw();  // 绘制一个圆形shapePtr2->draw();  // 绘制一个矩形return 0;
}

在上述示例中,我们将 Shape 类的 draw 函数声明为虚函数,并在其派生类 Circle 和 Rectangle 中重写了该函数。在 main 函数中,我们创建了一个基类对象 shape 和两个派生类对象 circle 和 rectangle,并将派生类对象的地址赋给基类指针。

通过基类指针调用虚函数时,程序会在运行时动态地确定调用哪个函数。因此,通过基类对象 shape 调用 draw 函数时,输出 "绘制一个形状";通过派生类对象的指针 shapePtr1 和 shapePtr2 调用 draw 函数时,分别输出 "绘制一个圆形" 和 "绘制一个矩形",实现了多态性。

虚析构函数

在C++中,我们不能将构造函数声明为虚函数,因为构造函数的调用是在实例化对象时发生的,而虚函数的实现是通过虚函数表来实现的。在实例化对象之前,对象还没有被创建,也没有内存空间可供调用虚函数,所以将构造函数声明为虚函数是没有意义的。

然而,析构函数可以被声明为虚函数,并且通常情况下我们将其声明为虚析构函数。通过将析构函数声明为虚函数,可以在删除指向派生类对象的基类指针时,根据实际所指向的对象类型动态绑定调用相应的析构函数,从而实现正确的对象内存释放。

当使用基类指针指向派生类对象时,如果基类的析构函数不是虚函数,那么在删除指针时只会调用基类的析构函数,而不会调用派生类的析构函数。这可能导致派生类对象中动态分配的资源没有得到正确释放,从而造成内存泄漏。

通过声明析构函数为虚函数,我们可以保证在删除基类指针时调用正确的析构函数,确保对象的内存得到正确释放,避免内存泄漏问题。

下面是一个使用虚析构函数的示例:

#include <iostream>
using namespace std;class Base {
public:virtual ~Base() {cout << "调用了基类的析构函数" << endl;}
};class Derived : public Base {
public:~Derived() {cout << "调用了派生类的析构函数" << endl;}
};int main() {Base* ptr = new Derived();delete ptr;return 0;
}

在上述示例中,我们定义了一个基类 Base 和一个派生类 Derived。基类 Base 的析构函数被声明为虚函数(使用 virtual 关键字),而派生类 Derived 的析构函数没有显式声明为虚函数。

在 main 函数中,我们创建了一个派生类对象的指针 ptr,并将其赋给基类指针。接着,我们使用 delete 关键字删除基类指针,这会触发析构函数的调用。

由于基类的析构函数是虚函数,因此在删除指针时,会调用正确的析构函数。输出结果为:

调用了派生类的析构函数
调用了基类的析构函数

可以看到,通过将基类的析构函数声明为虚函数,我们确保了派生类的析构函数也被正确地调用,从而释放了派生类对象中的资源。

纯虚函数

在C++中,纯虚函数是一种在基类中声明但没有实现的虚函数。纯虚函数的声明形式是在函数原型的末尾使用 "= 0" 进行标记。纯虚函数用于定义一个接口,要求派生类必须实现该函数。

virtual 返回值 函数名(形参)=0;

纯虚函数的作用是定义一个基类的接口,这个接口可以被派生类继承并实现。通过在基类中声明纯虚函数,我们可以强制派生类提供相应的实现,以确保派生类具有某些特定的行为或功能。

下面是一个使用纯虚函数的示例:

#include <iostream>
using namespace std;class Shape {
public:virtual double getArea() const = 0; // 纯虚函数
};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;}
};class Circle : public Shape {
private:double radius;
public:Circle(double r) : radius(r) {}double getArea() const override { // 派生类必须实现纯虚函数return 3.14159 * radius * radius;}
};int main() {Shape* shape1 = new Rectangle(5, 6);Shape* shape2 = new Circle(3);cout << "矩形的面积: " << shape1->getArea() << endl;cout << "圆形的面积: " << shape2->getArea() << endl;delete shape1;delete shape2;return 0;
}

在上述示例中,我们定义了一个基类 Shape,其中声明了一个纯虚函数 getArea()。这个函数没有提供实现,只是要求派生类必须实现它。派生类 Rectangle 和 Circle 都继承自基类 Shape,并实现了 getArea() 函数。

在 main 函数中,我们创建了基类指针 shape1 和 shape2,分别指向派生类对象 Rectangle 和 Circle。通过调用 getArea() 函数,我们可以获得矩形和圆形的面积。

注意到,基类 Shape 是抽象类,因为它包含一个纯虚函数。抽象类不能被实例化,只能用作其他类的基类。而派生类必须实现纯虚函数,否则它们也会成为抽象类。

这篇关于学习c++的第十二天的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/350056

相关文章

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<