【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】

发布于:2024-12-22 ⋅ 阅读:(9) ⋅ 点赞:(0)

目录😋

任务描述

相关知识

1. 纯虚函数

一、特点

二、使用场景

三、作用

四、注意事项

五、相关概念对比

2. 抽象类的使用

一、定义与概念

二、使用场景

编程要求

测试说明

我的通关代码:

测试结果:


任务描述

本关任务:

将直接插入排序、直接选择排序、冒泡排序、顺序查找函数封装到数组类 Array 中,作为成员函数。

相关知识

为了完成本关任务,你需要掌握:

  1. 纯虚函数
  2. 抽象类的使用

1. 纯虚函数

一、特点
  1. 函数声明形式
    纯虚函数在声明时有其特定的语法形式,如 virtual 函数类型 函数名(参数列表) = 0;。以之前提到的 Base 类中的 virtual void Func() = 0; 为例,virtual 关键字表明这是一个虚函数,而最后的 = 0 则明确指出它是纯虚函数,意味着该函数在当前类(这里是 Base 类)中不提供具体的函数实现(也就是没有函数体),仅预留函数名和参数列表等信息,等待派生类去完善其具体功能。
  2. 不可直接调用
    由于纯虚函数本身没有函数体,在基类层面它是不能被直接调用的。如果尝试在基类对象上调用纯虚函数,编译器会报错,因为它没有实际可执行的代码逻辑与之对应。例如:
    Base baseObj;
    baseObj.Func();  // 这样的调用会导致编译错误,因为Base类中Func是纯虚函数,无函数体
  3. 派生类要求
    纯虚函数必须在派生类中进行定义,否则该虚函数在派生类中依然保持为纯虚函数状态。当派生类实现了这个纯虚函数后,才能通过派生类的对象调用这个函数,且调用时执行的是派生类中定义的函数逻辑。例如:
    class Derived : public Base {
    public:
        void Func() override {
            // 在这里定义Func函数在Derived类中的具体实现逻辑
            std::cout << "This is the implementation of Func in Derived class." << std::endl;
        }
    };
    
    Derived derivedObj;
    derivedObj.Func();  // 调用Derived类中实现的Func函数,输出相应内容,编译通过
二、使用场景
  1. 实现多态性
    纯虚函数是实现面向对象编程中多态性的重要手段之一。基类定义了一系列纯虚函数作为接口,不同的派生类根据自身的特点和需求去具体实现这些函数,这样就可以通过基类指针或引用指向不同的派生类对象,并调用这些虚函数时,执行不同派生类中对应的函数实现,呈现出多态的行为。例如,设计一个图形类作为基类,有 draw() 这样的纯虚函数,然后派生出 Circle(圆形)、Rectangle(矩形)等具体图形类,每个派生类各自实现 draw() 函数来绘制对应的图形,通过基类指针可以统一操作不同图形对象的绘制操作,代码可能如下:
    class Shape {
    public:
        virtual void draw() = 0;
    };
    
    class Circle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a circle." << std::endl;
        }
    };
    
    class Rectangle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a rectangle." << std::endl;
        }
    };
    
    int main() {
        Shape* shapePtr1 = new Circle();
        Shape* shapePtr2 = new Rectangle();
        shapePtr1->draw();
        shapePtr2->draw();
        delete shapePtr1;
        delete shapePtr2;
        return 0;
    }

    在上述代码中,通过基类 Shape 的指针指向不同派生类对象,调用 draw 函数时,根据对象实际类型执行相应派生类中定义的绘制逻辑,体现了多态性。

  2.  定义抽象类
    含有纯虚函数的类被称为抽象类,抽象类不能实例化对象(像前面例子中直接创建 Base 类对象就会报错),它更多的是作为一种抽象的概念和接口规范存在,用于为派生类提供统一的函数接口框架,引导派生类去实现特定的功能,使得整个类层次结构在设计上更加清晰、规范,便于代码的扩展和维护。例如在设计一个动物类层次结构时,动物都有 makeSound() 这个行为,但不同动物发声方式不同,所以可以在基类 Animal 中定义 virtual void makeSound() = 0; 纯虚函数,然后各种具体动物类(如 DogCat 等)去实现这个函数来体现各自独特的叫声。

三、作用
  1. 接口规范作用
    纯虚函数在基类中定义了一个统一的函数接口,明确告知派生类需要实现哪些功能,保证了派生类在实现相关功能时有一致的函数签名(函数名、参数列表、返回类型等方面符合基类定义),有助于提高代码的可读性和可维护性。不同的开发人员在编写派生类时,能清楚知道需要遵循的接口规范,避免随意编写函数而导致代码逻辑混乱。
  2. 代码扩展性提升
    当需要在已有类层次结构基础上添加新的派生类时,只要按照基类中纯虚函数定义的接口去实现相应功能即可,不需要对基类或者其他已有派生类做大量修改。例如,在前面图形类的例子中,如果后续要添加一个 Triangle(三角形)类,只需从 Shape 类派生,然后实现 draw 函数来绘制三角形就行,原有操作图形绘制的代码(通过基类指针调用 draw 函数的部分)无需改动就能适用于新的三角形图形对象,方便了代码的扩展和功能的丰富。
四、注意事项
  1. 继承关系中的纯虚函数处理
    在多层继承结构中,如果中间层派生类没有对基类的纯虚函数进行定义,那么这个纯虚函数依然会传递下去,在该中间层派生类的派生类中依然需要进行定义才能实例化对象。例如:
    class Base {
    public:
        virtual void func() = 0;
    };
    
    class Intermediate : public Base {
    // 这里没有对func函数进行定义,func在Intermediate类中依然是纯虚函数
    };
    
    class Derived : public Intermediate {
    public:
        void func() override {
            // 在这里定义func函数实现,使得Derived类可以实例化对象
        }
    };
  2. 函数签名一致性
    派生类在定义纯虚函数时,必须保证函数签名(包括函数名、参数列表、返回类型,返回类型协变情况除外)与基类中纯虚函数的定义严格一致,否则编译器会认为是在重新定义一个新的函数,而不是实现基类中的纯虚函数,导致编译错误。例如:
    class Base {
    public:
        virtual int calculate(int num) = 0;
    };
    
    class Derived : public Base {
    public:
        double calculate(int num) override {  // 返回类型不一致,会导致编译错误
            return 0.0;
        }
    };
五、相关概念对比
  1. 虚函数与纯虚函数
    虚函数可以在基类中有具体的函数体实现,派生类可以选择重写(override)它来实现多态性,也可以不重写而直接继承基类的函数实现。而纯虚函数在基类中没有函数体,必须由派生类去定义实现,主要用于定义抽象类和接口规范,引导派生类进行特定功能的实现,以此来实现多态等面向对象编程特性。
  2. 抽象类与具体类
    含有纯虚函数的类就是抽象类,它不能被实例化,侧重于提供抽象的接口和概念框架。而具体类是可以实例化对象的类,通常是在抽象类基础上,通过实现其纯虚函数等方式,完善了具体功能,从而成为能够创建实际对象并使用的类,比如前面例子中的 CircleRectangle 等就是具体类,它们基于抽象的 Shape 类实现了具体绘制图形的功能,进而可以创建相应图形对象进行操作。

2. 抽象类的使用

一、定义与概念

    抽象类是一种不能被实例化的类,它通常包含一个或多个纯虚函数。纯虚函数是在声明时被初始化为 0 的虚函数,例如:virtual void func() = 0;。抽象类主要用于为派生类提供一个通用的接口规范,定义一系列的行为和属性,但把具体的实现细节留给派生类。
 

二、使用场景

 1、多态性实现

        假设要开发一个图形绘制程序,有多种图形如圆形、矩形等。可以先定义一个抽象类Shape作为基类,其中包含一个纯虚函数draw():

class Shape {
public:
    virtual void draw() = 0;
};

然后派生出具体的图形类,如CircleRectangle,它们分别实现draw()函数来绘制自己的形状:

class Circle : public Shape {
public:
    void draw() override {
        // 绘制圆形的具体代码,比如使用绘图库来绘制
        cout << "Drawing a circle." << endl;
    }
};
class Rectangle : public Shape {
public:
    void draw() override {
        // 绘制矩形的具体代码
        cout << "Drawing a rectangle." << endl;
    }
};

这样,通过基类指针或引用,可以方便地调用不同派生类的draw()函数,实现多态性。例如:

Shape* shapePtr;
shapePtr = new Circle();
shapePtr->draw();
delete shapePtr;
shapePtr = new Rectangle();
shapePtr->draw();
delete shapePtr;

2、代码结构规范:

        在大型项目中,抽象类可以用于规范代码结构。比如在一个游戏开发项目中,有不同类型的角色,如战士、法师等。可以定义一个抽象类Character,其中包含纯虚函数attack()move()等:

class Character {
public:
    virtual void attack() = 0;
    virtual void move() = 0;
};

战士类Warrior和法师类Mage等派生类实现这些纯虚函数来定义自己的攻击和移动方式。这种方式使得不同角色的行为定义有了统一的规范,便于代码的维护和扩展。例如:

class Warrior : public Character {
public:
    void attack() override {
        cout << "Warrior attacks with sword." << endl;
    }
    void move() override {
        cout << "Warrior runs quickly." << endl;
    }
};
class Mage : public Character {
public:
    void attack() override {
        cout << "Mage casts a spell." << endl;
    }
    void move() override {
        cout << "Mage teleports." << endl;
    }
};

3、继承关系中的注意事项

  • 纯虚函数的继承:如果基类是抽象类,派生类没有实现基类中的所有纯虚函数,那么派生类仍然是抽象类。例如:
    class Base {
    public:
        virtual void func1() = 0;
        virtual void func2() = 0;
    };
    class Derived : public Base {
    public:
        void func1() override {
            // 实现func1
        }
    };

    在这个例子中,Derived类仍然是抽象类,因为它没有实现func2,所以不能实例化Derived类的对象。

  • 函数签名一致性:派生类在实现抽象类中的纯虚函数时,要保证函数签名(包括函数名、参数类型、返回类型等)与抽象类中的定义一致。只有满足这个条件,才能正确地实现多态性。例如,如果抽象类中有一个纯虚函数virtual int calculate(double num) = 0;,派生类中实现的函数应该具有相同的函数名、参数类型和返回类型,如int calculate(double num) override {... }

4、与具体类的对比

        具体类是可以直接实例化对象的类,它实现了所有必要的功能。而抽象类侧重于定义接口和行为规范,不能直接创建对象。抽象类为具体类提供了一个模板,具体类通过继承抽象类并实现其纯虚函数来具体化抽象类所定义的概念。例如,在前面图形的例子中,CircleRectangle是具体类,可以创建它们的对象来表示具体的图形并进行绘制操作,而Shape是抽象类,用于规定所有图形类都应该有draw()这个行为,但本身不能用来创建一个没有具体形状的图形对象。

编程要求

在右侧编辑器中的Begin-End之间补充代码,设计图像基类、矩形类和圆形类三个类,函数成员变量据情况自己拟定,其他要求如下:

  • 图形类(Shape)

    • 纯虚函数:void PrintArea(),用于输出当前图形的面积。
  • 矩形类(Rectangle)

    • 继承关系:继承 Shape 类,并且重写 PrintArea 函数,输出矩形的面积,输出格式为:矩形面积 = width*height。
    • 带参构造函数:Rectangle(float w,float h),这两个参数分别赋值给成员变量的宽、高。
  • 圆形类(Circle)

    • 继承关系:继承 Shape 类,并且重写 PrintArea 函数,输出圆形的面积,输出格式为:圆形面积 = radio * radio * 3.14。
    • 带参构造函数:Circle(float r),参数 r 代表圆的半径。

测试说明

平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:

测试输入:

10 2.5

预期输出:

矩形面积 = 20
圆形面积 = 314

测试输入:

2 2.5

预期输出:

矩形面积 = 4
圆形面积 = 12.56

开始你的任务吧,祝你成功!


我的通关代码:

#include <iostream>
using namespace std;

/********* Begin *********/
class Shape
{
	//基类的声明
    
   public:
   virtual void PrintArea() = 0;
    
};

class Rectangle : public Shape
{
	//矩形类的声明
    public:
    void PrintArea()override;
    float width;
    float height;
    Rectangle(float w,float h);
};
//矩形类的定义
void Rectangle::PrintArea(){
    cout<<"矩形面积 = "<<width * height<<endl;
}
Rectangle::Rectangle(float w,float h){
    width = w;
    height = h;
}
class Circle : public Shape
{
	//圆形类的声明
    public:
    void PrintArea()override;
    float radio;
    Circle(float r);
};
//圆形类的定义
void Circle::PrintArea(){
    cout <<"圆形面积 = "<<radio * radio *3.14<<endl;
}
Circle::Circle(float r){
    radio = r;
}
/********* End *********/

测试结果:

在这里插入图片描述