C++多态、虚函数、纯虚函数、抽象类


多态的概念
        通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
        举个简单的例子:抢红包,我们每个人都只需要点击一下红包,就会抢到金额。有些人能抢到几十元,而有些人只能抢到几元甚至几毛。也正说明了不同的人做相同的事,结果却不同,这就是多态。

        在C++中有两种多态性,一种是静态的多态、一种是动态的多态;

静态的多态:函数重载,看起来调用同一个函数却有不同的行为。静态:原理是编译时实现。

动态的多态:一个父类的引用或指针去调用同一个函数,传递不同的对象,会调用不同的函数。动态:原理是运行时实现。


一、前言

        多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。下面的实例中,基类 Shape 被派生为两个类,如下所示:

#include <iostream> 
using namespace std;class Shape {
public:void area(){cout << "Parent class area :" << endl;}
};
class Rectangle : public Shape {
public:void area(){cout << "Rectangle class area :" << endl;}
};
class Triangle : public Shape {
public:void area(){cout << "Triangle class area :" << endl;}
};void func(Shape& p) {p.area();
}
// 程序的主函数
int main()
{Rectangle Rec;// 调用矩形的求面积函数 areafunc(Rec);Triangle Tri;// 调用三角形的求面积函数 areafunc(Tri);return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Parent class area :
Parent class area :

        导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。

        但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual,其余不变,如下所示:

#include <iostream> 
using namespace std;class Shape {
public:virtual void area(){cout << "Parent class area :" << endl;}
};
class Rectangle : public Shape {
public:void area(){cout << "Rectangle class area :" << endl;}
};
class Triangle : public Shape {
public:void area(){cout << "Triangle class area :" << endl;}
};void func(Shape& p) {p.area();
}
// 程序的主函数
int main()
{Rectangle Rec;// 调用矩形的求面积函数 areafunc(Rec);Triangle Tri;// 调用三角形的求面积函数 areafunc(Tri);return 0;
}

修改后,当编译和执行前面的实例代码时,它会产生以下结果:

Rectangle class area :
Triangle class area :

        此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。

        正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

二、多态的定义及实现

1.多态的构成条件 

        在继承中要构成多态还有两个条件:

        (1)必须通过基类的指针或者引用调用虚函数。

        (2)被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

2.虚函数

        虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
        我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定

        一旦定义了虚函数,该基类的派生类中同名函数也自动成为了虚函数。也就是说在派生类中有一个和基类同名的函数,只要基类加了virtual修饰,派生类不加virtual修饰也是虚函数。虚函数只能是类中的一个成员函数,不能是静态成员或普通函数。

        注意:我们在继承中为了解决数据冗余和二义性的问题,需要用到虚拟继承,关键字也是virtual,和多态中的virtual是没有关系的。

3.虚函数的重写

        虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

        通过对虚函数的重写,就能够实现多态:

#include<iostream>
using namespace std;//买票
class Person
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};//学生买票
class Student : public Person
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};//军人买票
class Soldier : public Person
{
public:void BuyTicket() { cout << "优先-买票-半价" << endl; }};//构成多态,传的哪个类型的对象,调用的就是这个类型的虚函数 --- 跟对象有关
//不构成多态,调用就是P的类型 --- 跟类型有关
void Func(Person& p) //或void Func(Person* p)
{p.BuyTicket();   //p->BuyTicket(); 
}int main()
{Person ps;Func(ps);   //没有任何身份去买票,一定是全价Student st;Func(st);   //以学生的身份去买票,是半价Soldier so;Func(so);   //以军人的身份去买票,是优先并且半价return 0;
}

4.虚函数重写的两个例外

(1).协变(基类与派生类虚函数返回值类型不同)

        派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。 

另一种解释:

        C++中的协变(Covariance)指的是派生类的返回类型可以是基类函数的返回类型的子类型。当一个派生类继承了一个基类,并且覆盖(override)了基类中的虚函数时,可以使用协变来改变返回类型。

        具体而言,如果基类函数的返回类型是指针或引用,那么派生类中覆盖该函数时,返回类型可以是基类返回类型所指向或引用的类型的派生类型。

实现协变需满足以下条件:

  • 基类中的函数必须是虚函数(使用 virtual 关键字声明)。
  • 派生类中重写的函数必须具有相同的函数签名(函数名、参数列表和常量性)。
  • 派生类中重写的函数的返回类型必须是基类函数返回类型的子类型。

示例:

引用自:C++协变(covariant)-CSDN博客

        假设有一个基类 Animal 和两个派生类 Dog 和 Cat。Animal 类中有一个虚函数 makeSound(),它返回一个指向 Animal 对象的指针。在派生类 Dog 中,可以重写 makeSound() 函数并返回一个指向 Dog 对象的指针。同样,在派生类 Cat 中也可以重写 makeSound() 函数并返回一个指向 Cat 对象的指针。

#include <iostream>
class Animal {
public:virtual Animal* makeSound() {std::cout << "Animal makes a sound." << std::endl;return this;}
};
class Dog : public Animal {
public:virtual Dog* makeSound() {std::cout << "Dog barks." << std::endl;return this;}
};
class Cat : public Animal {
public:virtual Cat* makeSound() {std::cout << "Cat meows." << std::endl;return this;}
};
int main() {Animal* animal;Dog dog;Cat cat;animal = &dog;animal->makeSound();  // Output: "Dog barks."animal = &cat;animal->makeSound();  // Output: "Cat meows."return 0;
}

协变与多态的区别: 

        C++中协变和多态是密切相关的。多态(Polymorphism)指的是同一个函数在不同的对象上被调用时,可以表现出不同的行为方式。

        在C++中,通过使用虚函数(virtual function),实现了运行时多态的语法机制。基类中的虚函数可以在派生类中被重写(覆盖),当派生类对象调用该虚函数时,会根据对象的实际类型来确定调用哪个虚函数。

        而协变则指的是派生类可以改变继承自基类函数的返回类型,使得返回类型成为基类返回类型所指向或引用的类型的派生类型。

        通过将协变和多态结合起来,我们可以在派生类中覆盖基类的虚函数,并且返回派生类特有的类型。这就允许我们在多态的情况下,在派生类中使用更具体的返回类型。

 (2).析构函数的重写(基类与派生类析构函数的名字不同) 

        如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

        在C++中,析构函数是一种特殊的成员函数,用于在对象销毁时执行清理工作。通常情况下,析构函数会自动由编译器生成,默认执行对象的成员变量和基类的析构函数。

        当需要对派生类进行额外的清理工作或资源释放时,可以通过重写(override)基类的析构函数来实现。

        在派生类中重写析构函数需要遵循以下规则:

  1. 函数名与基类的析构函数完全相同。
  2. 参数列表为空。
  3. 返回类型为空(void)。
  4. 可以添加override关键字(可选),以显式地说明正在重写基类的析构函数。

        以下是一个示例代码:

class Base {
public:virtual ~Base() {// 基类的析构函数}
};class Derived : public Base {
public:~Derived() override {// 派生类的析构函数,重写了基类的析构函数}
};

        在上述代码中,基类Base定义了一个虚析构函数,派生类Derived通过重写基类的析构函数,实现了自己的清理逻辑。

        需要注意的是,在继承关系中,如果基类的析构函数是一个虚函数,则派生类中的析构函数也应该声明为虚函数。这样,在使用基类指针或引用指向派生类对象,并通过该指针或引用调用析构函数时,能够正确地调用到派生类的析构函数。

        总之,通过在派生类中重写基类的析构函数,可以实现额外的清理工作或资源释放。重写析构函数需要遵循特定的规则,并且建议将基类的析构函数声明为虚函数。

 

另一种解释:

(引用自:C++ 多态(一) : 多态的构成条件、final、override、协变、析构函数的重写、抽象类_c++ 多态 override-CSDN博客)

        析构函数虽然函数名不同,但是也能构成重写,因为站在编译器的视角,他所调用的析构函数名称都叫做destructor。

为什么编译器要通过这种方式让析构函数也能构成重写呢?

假设我用一个基类指针或者引用指向派生类对象,如果不构成多态会怎样?

class Human
{
public:~Human(){cout << "~Human()" << endl;}
};class Student : public Human
{
public:~Student(){cout << "~Student()" << endl;}
};int main()
{Human* h = new Student;delete h;return 0;
}

输出结果: 

~Human()

分析:

        上述代码只会调用类Human的析构函数,即如果不构成多态,那么指针是什么类型的,就会调用什么类型的析构函数,这也就导致了一种情况,如果派生类的析构函数中有资源释放,而这里却没有释放掉那些资源,就会导致内存泄漏的问题。

        所以为了防止这种情况,必须要将析构函数定义为虚函数。这也就是编译器将析构函数重命名为destructor的原因。

class Human
{
public:virtual ~Human(){cout << "~Human()" << endl;}
};class Student : public Human
{
public:virtual ~Student() // 该virtual关键字可省略{cout << "~Student()" << endl;}
};int main()
{Human* h = new Student;delete h;return 0;
}

输出结果: 

~Student()
~Human() 

5.C++11 override和final

        从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了 override 和 final 两个关键字,可以帮助用户检测是否重写。

(1) final

        在C++11标准中,final是一个关键字,用于禁止继承和覆盖类的虚函数。当一个类或者一个类的成员函数被声明为final时,意味着它不能再被其他类继承或者它的虚函数不能被派生类覆盖。

使用final关键字的好处是:

  1. 可以增强代码的安全性:使用final关键字可以防止不恰当的继承和覆盖。
  2. 可读性:使用final关键字可以增强代码的可读性和可维护性,明确了类或函数的意图。

final:修饰虚函数,表示该虚函数不能再被重写。


#include<iostream>
class Car
{
public:virtual void Drive() final{}
};class Benz :public Car
{
public:virtual void Drive() override //检查是否完成重写{std::cout << "Benz-舒适" << std::endl;}
};
int main() {Benz benz;benz.Drive(); 
}

 上述程序因为final关键字的存在会报错,报错原因是:

final:修饰类,表示该类不能再被继承。

示例:


#include<iostream>
class Car final
{
public:virtual void Drive() {}
};class Benz :public Car 
{
public:virtual void Drive()  //检查是否完成重写{std::cout << "Benz-舒适" << std::endl;}
};
int main() {Benz benz;benz.Drive(); 
}

上述程序报错: 

不能将final用于基类,否则程序报错! 

(2) override

        在C++中,override是一个特殊的关键字,用于显式地标识派生类中的函数是覆盖(override)基类中的虚函数。

        当派生类中的函数与基类中的虚函数具有相同的名称、参数列表和返回类型时,可以使用override关键字来明确指示该函数是对基类函数的覆盖。

使用override关键字的好处是:

  1. 错误检查:编译器会在编译时检查是否存在函数覆盖错误。如果派生类中使用了override关键字,但没有正确地覆盖基类中的虚函数,编译器将报错。
  2. 可读性:使用override关键字可以增强代码的可读性和可维护性,明确了派生类函数的意图。

下面是一个示例代码:


#include<iostream>
class Car
{
public:virtual void Drive(){}
};class Benz :public Car
{
public:virtual void Drive() override //检查是否完成重写{std::cout << "Benz-舒适" << std::endl;}
};
int main() {Benz benz;benz.Drive(); // Benz-舒适
}

三、抽象类

纯虚函数

        您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

        在虚函数的后面写上 = 0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。

class Shape {public:// pure virtual functionvirtual int area() = 0;
};

   = 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数

包括纯虚函数的类叫做抽象类,也叫接口类,抽象类不能实例化出对象。

示例:

#include<iostream>
//抽象类
class Car
{
public:virtual void Drive() = 0;//纯虚函数 
};int main()
{Car c;//抽象类不能实例化出对象return 0;
}

上述程序运行报错: 

 派生类继承后也不能实例化出对象。

示例:

#include<iostream>
class Car
{
public:virtual void Drive() = 0; // 纯虚函数
};
class Benz :public Car{};
int main()
{Benz b1;
}

上述程序运行出错: 

        派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

示例:

#include<iostream>
class Car
{
public://纯虚函数一般只声明,不实现(可以实现,但没有价值,因为不能实例化出对象,可以定义指针或引用)virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){std::cout << "Benz-舒适" << std::endl;}
};class BMW :public Car
{
public:virtual void Drive(){std::cout << "BMW-操控" << std::endl;}
};int main()
{//派生类只有重写了纯虚函数才能实例化出对象Benz b1;BMW b2;//通过基类的指针去调用不同对象的函数Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

输出结果: 

Benz-舒适
BMW-操控

接口继承和实现继承

  • 普通函数的继承是一种实现继承,派生类继承了基类的普通成员函数,可以使用函数,继承的是函数的实现。
  • 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
  • 所以如果不实现多态,不要把函数定义成虚函数。

参考自(很值得学习):

【精选】【C++】—— 多态_c++多态_霄沫凡的博客-CSDN博客

注:参考内容只是为了自身学习,并无其他想法!!!

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

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

相关文章

OpenCV中world模块介绍

OpenCV中有很多模块&#xff0c;模块间保持最小的依赖关系&#xff0c;用户可以根据自己的实际需要链接相关的库&#xff0c;而不需链接所有的库&#xff0c;这样在最终交付应用程序时可以减少总库的大小。但如果需要依赖OpenCV的库太多,有时会带来不方便&#xff0c;此时可以使…

vue2 element手术麻醉信息系统源码,手术预约、手术安排、排班查询、手术麻醉监测、麻醉记录单

手术麻醉临床信息系统有着完善的临床业务功能&#xff0c;能够涵盖整个围术期的工作&#xff0c;能够采集、汇总、存储、处理、展现所有的临床诊疗资料。通过该系统的实施&#xff0c;能够规范麻醉科的工作流程&#xff0c;实现麻醉手术过程的信息数字化&#xff0c;自动生成麻…

mac 升级node到指定版本

node版本14.15.1升级到最新稳定版18.18.2 mac系统 先查看一下自己的node版本 node -v开始升级 第一步 清除node的缓存 sudo npm cache clean -f第二步 安装n模块【管理模块 n是管理 nodejs版本】 sudo npm install -g n第三步升级node sudo n stable // 把当前系统的 Node…

计算机毕业设计 基于SpringBoot智慧养老中心管理系统的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【趣味随笔】盘点国内外做双足机器人的公司

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

leetCode 392. 判断子序列 动态规划 + 优化空间 / 双指针 等多种解法

392. 判断子序列 - 力扣&#xff08;LeetCode&#xff09; 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c…

Aocoda-RC F405V2 FC(STM32F405RGT6 v.s. AT32F435RGT7) IO Definitions

[TOC](Aocoda-RC F405V2 FC(STM32F405RGT6 v.s. AT32F435RGT7) IO Definitions) 1. 源由 Aocoda-RC F405V2飞控支持betaflight/inav/Ardupilot固件&#xff0c;是一款固件兼容性非常不错的开源硬件。 之前我们对比过STM32F405RGT6 v.s. AT32F435RGT7 Comparison for Flight …

ThreadLocal源码解密

1 背景 作为一只懒懒地程序员,其实我是不太爱看源码的,晦涩、深奥、难懂、耗费时间等等,就觉得不是我这种能力平平地小老百姓能吃得消的,但现实比人强,记得曾经我就被不懂原理的情况下乱用ThreadLocal给毒打了。 犹记得当时在一个JSF服务中的责任链的校验场景中需要在源…

UART、SPI、I2C通信协议超全入门教程

本文引注: https://mp.weixin.qq.com/s/lVWK8xlDt7cOLi8WHYSuPg 1.SPI协议 1.基础 2.简介 3.工作原理 4.SPI数据传输步骤与优缺点 2.UART协议

分布式微服务技术栈-SpringCloud<Eureka,Ribbon,nacos>

微服务技术栈 一、微服务 介绍了解1 架构结构案例与 springboot 兼容关系拆分案例拆分服务拆分-服务远程调用 2 eureka注册中心Eureka-提供者与消费者Eureka-eureka原理分析Eureka-搭建eureka服务Eureka-服务注册Eureka-服务发现 3 Ribbon组件 负载均衡Ribbon-负载均衡原理Ribb…

python随手小练6

1、汉诺塔 汉诺塔&#xff1a;汉诺塔&#xff08;又称河内塔&#xff09;问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子&#xff0c;在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放…

ubuntu终端命令行下如何使用NetworkManager(netplan)来配置wifi网络

最近在给家里折腾一个文件共享服务器给家里的小米摄像头保存监控视频用。树莓派太贵了&#xff0c;找来找去发现香橙派orangepi zero3 是最低成本的替代解决方案&#xff08;网络足够快&#xff0c;CPU的IO能力足够强&#xff09;&#xff0c;香橙派orangepi zero3的操作系统是…

学信息系统项目管理师第4版系列33_信息化发展

1. 企业信息化发展战略要点 1.1. 【高22下选12】 1.2. 以信息化带动工业化 1.3. 信息化与企业业务全过程的融合、渗透 1.4. 信息产业发展与企业信息化良性互动 1.5. 充分发挥政府的引导作用 1.6. 高度重视信息安全 1.7. 企业信息化改组改造和形成现代企业制度有机结合 …

虚拟化、容器与Docker基本介绍以及安装部署(Docker 基本管理)

目录 1 Docker 概述 1.1 Docker与虚拟机的区别 1.2 容器在内核中支持2种重要技术 1.3 Docker核心概念 2 安装 Docker 2 Docker 镜像操作 2.1 搜索镜像 2.2 获取镜像 2.3 镜像加速下载 2.4 查看镜像信息 2.4.1 查看下载的镜像文件信息 2.4.2 查看下载到本地的所有镜像…

RabbitMQ队列及交换机的使用

目录 一、简单模型 1、首先控制台创建一个队列 2、父工程导入依赖 3、生产者配置文件 4、写测试类 5、消费者配置文件 6、消费者接收消息 二、WorkQueues模型 1、在控制台创建一个新的队列 2、生产者生产消息 3、创建两个消费者接收消息 4、能者多劳充分利用每一个消…

400 The plain HTTP request was sent to HTTPS port

接口请求发生问题&#xff1a; 解决方法&#xff1a; Nginx HTTP服务器的报错 “400 Bad Request: The plain HTTP request was sent to HTTPS port”&#xff0c;本文将讲解如何解决这个问题。简单从报错的字面意思上来看&#xff0c;是因为HTTP请求被发送到HTTPS端口&#x…

CRM自动化意味着什么?企业如何从中受益?

客户关系管理&#xff08;CRM&#xff09;软件不再仅仅适用于大公司或销售周期长的行业&#xff0c;它越来越成为各种规模企业的重要工具。 在日常工作中&#xff0c;当你陷入流程的所有细节时&#xff0c;可能会产生不必要的工作。因此&#xff0c;如果你想要CRM提供的组织和…

【Javascript】基础数据类型

目录 基础数据类型 1.number 字面量声明 数字对象方式声明 整数判断 指定返回小数位数 NaN-表示非数字值 浮点精度 解决误差 String 字面量声明 数字对象声明 连接运算符 获取长度 大小写转换 转换成大写 转换成小写 ​编辑 移除空白 获取单字符 ​编辑 截…

不想加班的小伙伴们,请把这四个神器焊在电脑上~

今天又来给大家分享干货啦&#xff0c;如果你下载视频没渠道&#xff0c;写方案没灵感思路&#xff0c;做表格太慢&#xff0c;做海报太复杂&#xff0c;那你一点要看这一篇&#xff0c;今天分享的四个宝藏网站专门解决以上问题&#xff0c;一起来看看吧&#xff01; 一、WeDow…

4、Kafka 消费者

5.1 Kafka 消费方式 5.2 Kafka 消费者工作流程 5.2.1 消费者总体工作流程 5.2.2 消费者组原理 Consumer Group&#xff08;CG&#xff09;&#xff1a;消费者组&#xff0c;由多个consumer组成。形成一个消费者组的条件&#xff0c;是所有消费者的groupid相同。 • 消费者组内…