🎬慕斯主页:修仙—别有洞天
♈️今日夜电波:世界上的另一个我
1:02━━━━━━️💟──────── 3:58
🔄 ◀️ ⏸ ▶️ ☰
💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍
目录
首先了解形成多态的条件
接着了解多态的“三同”规则
思考
C++11 override 和 final
抽象类
首先了解形成多态的条件
在C++中,形成多态主要依赖于以下几个条件:
- 继承:多态通常发生在类与类的继承关系中。一个基类(或称为父类)可以有一个或多个派生类(或称为子类),派生类继承并可能扩展基类的行为。
- 虚函数:在基类中声明虚函数是实现运行时多态的关键。通过在基类中使用
virtual
关键字声明成员函数,可以在派生类中重写这些函数,提供不同的实现。- 动态绑定(迟绑定):当通过基类指针或引用调用虚函数时,具体调用哪个版本的函数(基类还是派生类的)是在程序运行时决定的,这称为动态绑定或迟绑定。这是多态行为的核心机制。
- 基类指针或引用:要实现多态,通常需要使用指向基类的指针或引用来操作派生类对象。这样,可以通过相同的接口来处理不同类型的派生类对象。
- 派生类重写虚函数:派生类必须重写基类中的虚函数以提供自己的实现。如果派生类没有重写虚函数,它将直接继承基类中的版本。
- 访问权限:确保派生类有足够的访问权限来重写基类中的虚函数。例如,如果基类的虚函数是私有的,则除了友元类之外,派生类将无法重写它。
- 析构函数的多态性:为了使通过基类指针删除派生类对象时能够正确调用派生类的析构函数,基类的析构函数应该被声明为虚函数。
- 纯虚函数和抽象基类:如果基类包含纯虚函数(使用
= 0
声明的虚函数),那么这个基类就是一个抽象基类,不能被实例化。派生类必须提供所有纯虚函数的实现,才能成为可实例化的具体类。- 类型安全:C++中的RTTI(运行时类型信息)机制,如
typeid
和dynamic_cast
,允许在运行时检查和转换对象的类型,增加了多态的安全性。- 编译器和内存管理:C++编译器在后台维护虚函数表(vtable)和管理动态内存分配,以确保多态行为的正确实现。
简简单单的先看个例子吧:
#include<iostream>
using namespace std;
class Person
{
public:
virtual void publish()
{
cout << "i am a Person" << endl;
}
virtual ~Person() { cout << "~Person()" << endl; }
};
class Student:public Person
{
public:
virtual void publish()
{
cout << "i am a Student" << endl;
}
virtual ~Student() { cout << "~Student()" << endl; }
};
void fun(Person& a)
{
a.publish();
}
void fun2(Person* A)
{
A->publish();
}
int main()
{
Person* cc = new Person;
Student* ccc = new Student;
fun2(cc);
fun2(ccc);
delete ccc;
delete cc;
cout << "_________________" << endl;
Person aa;
Student aaa;
fun(aa);
fun(aaa);
return 0;
}
接着了解多态的“三同”规则
在C++中,多态性(Polymorphism)主要指的是运行时多态,它允许不同类的对象通过相同的接口调用适当的方法。这种特性通常归纳为“三同”原则:
- 同函数名(Same Function Name):基类和派生类中的虚函数必须具有相同的函数名。这样,当通过基类的指针或引用调用该函数时,可以根据对象的实际类型来调用正确的函数实现。
- 同参数列表(Same Parameter List):基类和派生类中的虚函数应该具有相同的参数列表。这意味着函数的参数数量和类型应该相同,以确保在调用时传递的参数是一致的。
- 同返回类型(Same Return Type):基类和派生类中的虚函数应具有相同的返回类型,或者派生类中的返回类型应该是基类中返回类型的子类型(C++11起支持协变返回类型)。这是为了确保在使用基类指针或引用调用虚函数时,返回值的类型是一致的,从而可以进行正确的赋值或操作。
需要注意的是,从C++11开始,引入了返回类型协变的概念,即派生类重写的虚函数可以返回基类中虚函数返回类型的子类型。例如:
class Base {
public:
virtual Base* clone() const {
// ...
}
};
class Derived : public Base {
public:
virtual Derived* clone() const override {
// ...
}
};
在这个例子中,Derived::clone
函数的返回类型是 Derived*
,它是 Base::clone
函数返回类型 Base*
的子类型,满足协变的要求。
思考
请你思考一下以下代码的执行结果:
class A
{
public:
virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
virtual void test() { func(); }
};
class B : public A
{
public:
void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
B* p = new B;
p->test();
return 0;
}
如下给出几个选项:
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
答案为:B
分析如下:
首先使用B类型的指针p指向了新申请的一块B类型的空间,然后调用由父类A继承下来的test()函数,此时需要注意的是:由于是父类继承下来的test()函数,那么这个函数的原型是不会改变的,那其中的this指针也是不会改变的(意思就是类型为A* 的this指针)。接下来就是多态的概念的了,是一个多态调用,派生类是可以不加virtual的,因为他会继承父类的函数声明,而子类会重写父类的实现,这里调用的是B的func()。需要注意的是:父类与子类func()中的参数列表是遵循“三同原则”的。因此我们使用的是A的func的声明,也就是:virtual void func(int val = 1),但是使用的是子类的实现:{ std::cout << "B->" << val << std::endl; }。这里又有一点注意事项:只有构成多态子类才会继承父类的接口,如果不构成那么就是正常调用!
C++11 override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
- final:修饰虚函数,表示该虚函数不能再被重写。修饰类,表示该类不能被继承。
//原型
class Car
{
public:
virtual void Drive() {}
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "Benz-舒适" << endl; }
};
//修饰类
class Car final
{
public:
virtual void Drive() {}
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "Benz-舒适" << endl; }
};
//修饰虚函数
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "Benz-舒适" << endl; }
};
2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
抽象类
抽象类是一种特殊的类,它不能被实例化,只能作为其他类的基类使用。
在C++中,抽象类是一种特殊的类,它包含了至少一个纯虚函数(pure virtual function)。纯虚函数是一种没有实现的虚函数,它的声明以“= 0”结尾,表示该函数没有具体的实现,需要在派生类中被重写。
抽象类的主要用途是为其他类提供一个公共的接口或基本的功能实现,同时强制要求派生类提供某些特定功能的实现。这样做的目的是为了保证派生类遵循某种规范或者具有某些特定的行为。由于抽象类不能被实例化,因此通常用来作为基类,通过继承机制来指导派生类的行为和属性。
除了纯虚函数,抽象类也可以包含普通的成员函数和数据成员。这些普通成员可以在抽象类中实现,也可以留空让派生类去实现。如果一个类继承了抽象类但没有实现所有的纯虚函数,那么这个类也必须被声明为抽象类。
#include<iostream>
using namespace std;
class car
{
public:
virtual void drive() = 0
{}
};
class BMW : public car
{
public:
virtual void drive()
{
cout << "i have a BMW" << endl;
}
};
class Benz : public car
{
public:
virtual void drive()
{
cout << "i have a Benz" << endl;
}
};
void fun(car& aa)
{
aa.drive();
}
int main()
{
BMW a;
Benz b;
fun(a);
fun(b);
return 0;
}
感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o!
给个三连再走嘛~