目录
2.3.2虚函数重写的细节(参数列表、调用子类虚函数的实质、缺virtual、协变、虚析构函数)
1.多态的概念
多态,就是多种形态
在C++中指相同的接口或方法调用能根据对象的实际类型表现出不同的行为
例如:对于买高铁票这个行为 ,
普通成年人买票时是成人票,学生买票时是学生票,军人买票时是优先买票,
即完成某个行为时,当不同的对象去完成时会产生出不同的状态
2.多态的定义及实现
2.1多态构成的条件
多态是在不同继承关系的类对象去调用同一函数时,产生的不同的行为
那么在继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用去调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
class Person
{
public:
virtual void BuyTicket()
{
cout << "票价-成人票" << endl;
}
};
class Student:public Person
{
public:
virtual void BuyTicket()
{
cout << "票价-学生票" << endl;
}
};
void Func(Person* p)
{
p->BuyTicket();
}
int main()
{
Person p;
Func(&p);
Student s;
Func(&s);
return 0;
}
2.2虚函数的概念
被virtual关键字声明的非静态成员函数(且非构造函数)称为虚函数
class Person { public: virtual void BuyTicket() { cout << "票价-成人票" << endl; } };BuyTicket就是虚函数
2.3虚函数的重写(覆盖)
派生类中有一个跟基类完全相同的虚函数
(即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同)
称子类的虚函数重写了基类的虚函数 即virtual + 三同
class Person
{
public:
virtual void BuyTicket()
{
cout << "票价-成人票" << endl;
}
};
class Student:public Person
{
public:
virtual void BuyTicket()
{
cout << "票价-学生票" << endl;
}
};
上述代码中,子类Student中的虚函数BuyTicket就重写了父类Person中的BuyTicket虚函数
2.3.2虚函数重写的细节(参数列表、调用子类虚函数的实质、缺virtual、协变、虚析构函数)
(1)重写满足的条件中,
参数列表相同实质上要求的是数据类型及其顺序相同
不要求形参名、缺省值相同
class Base { public: virtual void foo(int a, double b) { /* ... */ } }; class Derived : public Base { public: virtual void foo(int x, double y) override { /* ... */ } // 合法:参数类型和顺序相同 };class Base { public: virtual void baz(int a = 0) { /* ... */ } }; class Derived : public Base { public: virtual void baz(int a) override { /* ... */ } // 合法:默认参数值不影响重写 };
(2)子类虚函数重写基类的虚函数后,多态调用子类虚函数时仅执行子类的函数体
class A { public: virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; } }; class B : public A { public: virtual void func(int val = 0) { std::cout << "B->" << val << std::endl; } }; int main(int argc, char* argv[]) { A* p = new B; p->func(); return 0; }
(3)C++标准明确指出,
派生类中重写基类虚函数的函数默认具有虚函数性质,无论是否显式添加virtual
class Base { public: virtual void baz(int a = 0) { /* ... */ } }; class Derived : public Base { public: void baz(int a) override { /* ... */ } };可以理解为:一旦某个函数在基类中被声明为virtual,其所有派生类中的重写函数自动成为虚函数,无论是否显式标记
但是显式添加 virtual 可以提高代码的可读性,明确表示该函数是虚函数
(4)协变(基类与派生类虚函数返回值类型不同)
即基类虚函数返回基类对象的指针或者引用
派生类虚函数返回派生类对象的指针或者引用 的现象称为协变
class A{}; class B : public A {}; class Person { public: virtual A* f() {return new A;} }; class Student : public Person { public: virtual B* f() {return new B;} };
- 基类虚函数返回基类指针时:
派生类虚函数可以返回派生类指针(或派生类的派生类指针),但必须是基类返回类型的派生类类型。- 基类虚函数返回基类派生类的指针时:
派生类虚函数可以返回派生类的派生类指针,但必须是基类返回类型的派生类类型。- 基类虚函数返回基类引用时:
派生类虚函数可以返回派生类引用(或派生类的派生类引用),但必须是基类返回类型的派生类类型。- 基类虚函数返回基类派生类的引用时:
派生类虚函数可以返回派生类的派生类引用,但必须是基类返回类型的派生类类型。
(5)析构函数的重写
class A { public: ~A() { cout << "~A()" << endl; } }; class B : public A { public: ~B() { cout << "~B()" << endl; delete[] _a; } protected: int* _a = new int[10]; }; int main() { A* p = new B; delete p; return 0; }
此时不满足多态条件,delete只会根据P的类型去调用A的析构函数,造成内存泄漏
class A { public: virtual ~A() { cout << "~A()" << endl; } }; class B : public A { public: virtual ~B() { cout << "~B()" << endl; delete[] _a; } protected: int* _a = new int[10]; }; int main() { A* p = new B; delete p; return 0; }在基类的析构函数前添加virtual关键字即可构建多态,从而正确释放内存
实际上,编译器底层将类析构函数统一处理为destructor (或类似的内部表示)
只要基类的析构函数声明为虚函数,就可以确保通过基类指针删除派生类对象时,能够触发多态机制,即调用到派生类的析构函数,从而正确释放派生类资源
在实践中,基类析构函数通常声明为虚函数,以避免派生类资源泄漏
2.4 C++11 override 和 final
override 是一个关键字,
用来检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

final也是一个关键字,它有两个作用
(1)修饰虚函数,表示该虚函数不能再被重写
(2)将 final 关键字放在类定义的末尾,可以防止其他类继承该类
2.5 重载、重定义(隐藏)、重写(覆盖)的对比
(1)重载就是指在同一个作用域(如同一个类)中,存在函数名相同但参数列表(参数类型、参数个数或参数顺序)不同的多个函数的现象
(2)重定义是指在继承体系下,子类定义了与父类同名的非虚函数(或子类未重写父类的虚函数),导致父类的同名函数在子类作用域中被隐藏的现象
(3)重写是指在继承体系下,子类针对父类的虚函数重新定义函数体,以实现多态机制的现象
在继承体系下,子类和父类的同名函数不构成重写,就构成重定义
3.抽象类
3.1抽象类的概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数
class Car { public: virtual void Drive() = 0; };纯虚函数只能在类中声明,可以没有默认实现(如果要实现,只能在类外实现)。派生类可以通过显式调用基类的作用域(如
Base::func())来调用纯虚函数的实现
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象
抽象类的派生类可以实例化出对象,但必须重写基类的所有纯虚函数。如果派生类没有重写纯虚函数,则派生类也是抽象类,不能实例化class Car { public: virtual void Drive() = 0; }; class Benz :public Car { public: virtual void Drive() { cout << "Benz-舒适" << endl; } }; class BMW :public Car { public: virtual void Drive() { cout << "BMW-操控" << endl; } }; void Test() { Car* pBenz = new Benz; pBenz->Drive(); Car* pBMW = new BMW; pBMW->Drive(); } int main() { Test(); return 0; }
3.2 接口继承 与 实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数的实现,可以直接使用基类的函数。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,以实现多态。因此,如果不需要多态,可以不将函数定义为虚函数





1154

被折叠的 条评论
为什么被折叠?



