本关任务: 将直接插入排序、直接选择排序、冒泡排序、顺序查找函数封装到数组类 Array 中,作为成员函数。
为了完成本关任务,你需要掌握:
函数声明形式
纯虚函数在声明时有其特定的语法形式,如 virtual 函数类型 函数名(参数列表) = 0;
。以之前提到的 Base
类中的 virtual void Func() = 0;
为例,virtual
关键字表明这是一个虚函数,而最后的 = 0
则明确指出它是纯虚函数,意味着该函数在当前类(这里是 Base
类)中不提供具体的函数实现(也就是没有函数体),仅预留函数名和参数列表等信息,等待派生类去完善其具体功能。
不可直接调用 由于纯虚函数本身没有函数体,在基类层面它是不能被直接调用的。如果尝试在基类对象上调用纯虚函数,编译器会报错,因为它没有实际可执行的代码逻辑与之对应。例如:
Base baseObj;
baseObj.Func(); // 这样的调用会导致编译错误,因为Base类中Func是纯虚函数,无函数体
派生类要求 纯虚函数必须在派生类中进行定义,否则该虚函数在派生类中依然保持为纯虚函数状态。当派生类实现了这个纯虚函数后,才能通过派生类的对象调用这个函数,且调用时执行的是派生类中定义的函数逻辑。例如:
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函数,输出相应内容,编译通过
实现多态性
纯虚函数是实现面向对象编程中多态性的重要手段之一。基类定义了一系列纯虚函数作为接口,不同的派生类根据自身的特点和需求去具体实现这些函数,这样就可以通过基类指针或引用指向不同的派生类对象,并调用这些虚函数时,执行不同派生类中对应的函数实现,呈现出多态的行为。例如,设计一个图形类作为基类,有 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
函数时,根据对象实际类型执行相应派生类中定义的绘制逻辑,体现了多态性。
定义抽象类
含有纯虚函数的类被称为抽象类,抽象类不能实例化对象(像前面例子中直接创建 Base
类对象就会报错),它更多的是作为一种抽象的概念和接口规范存在,用于为派生类提供统一的函数接口框架,引导派生类去实现特定的功能,使得整个类层次结构在设计上更加清晰、规范,便于代码的扩展和维护。例如在设计一个动物类层次结构时,动物都有 makeSound()
这个行为,但不同动物发声方式不同,所以可以在基类 Animal
中定义 virtual void makeSound() = 0;
纯虚函数,然后各种具体动物类(如 Dog
、Cat
等)去实现这个函数来体现各自独特的叫声。
Triangle
(三角形)类,只需从 Shape
类派生,然后实现 draw
函数来绘制三角形就行,原有操作图形绘制的代码(通过基类指针调用 draw
函数的部分)无需改动就能适用于新的三角形图形对象,方便了代码的扩展和功能的丰富。继承关系中的纯虚函数处理 在多层继承结构中,如果中间层派生类没有对基类的纯虚函数进行定义,那么这个纯虚函数依然会传递下去,在该中间层派生类的派生类中依然需要进行定义才能实例化对象。例如:
class Base {
public:
virtual void func() = 0;
};
class Intermediate : public Base {
// 这里没有对func函数进行定义,func在Intermediate类中依然是纯虚函数
};
class Derived : public Intermediate {
public:
void func() override {
// 在这里定义func函数实现,使得Derived类可以实例化对象
}
};
函数签名一致性 派生类在定义纯虚函数时,必须保证函数签名(包括函数名、参数列表、返回类型,返回类型协变情况除外)与基类中纯虚函数的定义严格一致,否则编译器会认为是在重新定义一个新的函数,而不是实现基类中的纯虚函数,导致编译错误。例如:
class Base {
public:
virtual int calculate(int num) = 0;
};
class Derived : public Base {
public:
double calculate(int num) override { // 返回类型不一致,会导致编译错误
return 0.0;
}
};
override
)它来实现多态性,也可以不重写而直接继承基类的函数实现。而纯虚函数在基类中没有函数体,必须由派生类去定义实现,主要用于定义抽象类和接口规范,引导派生类进行特定功能的实现,以此来实现多态等面向对象编程特性。Circle
、Rectangle
等就是具体类,它们基于抽象的 Shape
类实现了具体绘制图形的功能,进而可以创建相应图形对象进行操作。 抽象类是一种不能被实例化的类,它通常包含一个或多个纯虚函数。纯虚函数是在声明时被初始化为 0 的虚函数,例如:virtual void func() = 0;
。抽象类主要用于为派生类提供一个通用的接口规范,定义一系列的行为和属性,但把具体的实现细节留给派生类。
1、多态性实现:
假设要开发一个图形绘制程序,有多种图形如圆形、矩形等。可以先定义一个抽象类Shape
作为基类,其中包含一个纯虚函数draw():
class Shape {
public:
virtual void draw() = 0;
};
然后派生出具体的图形类,如Circle
和Rectangle
,它们分别实现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、与具体类的对比
具体类是可以直接实例化对象的类,它实现了所有必要的功能。而抽象类侧重于定义接口和行为规范,不能直接创建对象。抽象类为具体类提供了一个模板,具体类通过继承抽象类并实现其纯虚函数来具体化抽象类所定义的概念。例如,在前面图形的例子中,Circle
和Rectangle
是具体类,可以创建它们的对象来表示具体的图形并进行绘制操作,而Shape
是抽象类,用于规定所有图形类都应该有draw()
这个行为,但本身不能用来创建一个没有具体形状的图形对象。
在右侧编辑器中的Begin-End之间补充代码,设计图像基类、矩形类和圆形类三个类,函数成员变量据情况自己拟定,其他要求如下:
void PrintArea()
,用于输出当前图形的面积。Rectangle(float w,float h)
,这两个参数分别赋值给成员变量的宽、高。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 *********/