书接上回
虚函数
虚函数是重载的另一种表现形式,这是一种动态的重载方式,提供了运行时的多态性,即动态联编 。
引入
先给出一个引子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <bits/stdc++.h> using namespace std;class Base { public : int v = 0 ; Base (int x) : v (x) {} Base () = default ; void printv () { cout << "This is from the Base method call: " << v << endl; } }; class Derived : public Base { public : Derived (int x) : Base (x) {} Derived () = default ; void printv () { cout << "This is from the Derived method call: " << v << endl; } }; int main () { Derived obj (555 ) ; obj.printv (); Base* bp; bp = &obj; bp->printv (); return 0 ; }
output 1 2 This is from the Derived method call: 555 This is from the Base method call: 555
在这个引子中,Base
类型指针指向了 obj
这个派生对象,在 前面的部分
我们直到,基类可以兼容派生类,但是只能使用从基类继承来的方法、成员,因此第二个输出为对基类同名方法的调用,这是不符合预期的,因为我们希望调用派生类重定义后的成员函数,而不是基类的函数。为了解决这个问题,我们可以使用虚函数 。
使用对象指针是为了表现一种动态形式,通过指针指向的不同,可以调用不同的成员函数。
声明虚函数
将上面例子中的 printv
重新声明为虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <bits/stdc++.h> using namespace std;class Base { public : int v = 0 ; Base (int x) : v (x) {} Base () = default ; virtual void printv () { cout << "This is from the Base method call: " << v << endl; } }; class Derived : public Base { public : Derived (int x) : Base (x) {} Derived () = default ; void printv () { cout << "This is from the Derived method call: " << v << endl; } }; int main () { Derived obj (555 ) ; obj.printv (); Base* bp; bp = &obj; bp->printv (); return 0 ; }
output 1 2 This is from the Derived method call: 555 This is from the Derived method call: 555
可以看到,两个输出调用的都是派生类的成员函数。
我们可以使用 virtual
关键字修饰一个成员函数的声明,但我们不需要在类外部定义该函数时加上这一关键字。
在基类的某个成员函数被声明为虚函数后,我们可以在其派生类中重定义它。但在重定义时,需要保持函数类型、函数名、参数个数、参数类型的顺序的一致,而
virtual
关键字则是可选添加,在派生类中符合重新定义要求的同名函数默认都是虚函数。
当我们没有在派生类中重新定义基类的虚函数时,则派生类会默认继承基类的虚函数。
虚函数必须是成员函数,而不是友元函数,也不能是静态成员函数
虚析构函数
C++中不存在虚构造函数,但存在虚析构函数。
以下面的例子说明为什么我们需要虚析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <bits/stdc++.h> using namespace std;class Base { public : int v = 0 ; Base (int x) : v (x) {} ~Base () { cout << "Destructed Base" << endl; } Base () = default ; virtual void printv () { cout << "This is from the Base method call: " << v << endl; } }; class Derived : public Base { public : Derived (int x) : Base (x) {} Derived () = default ; ~Derived () { cout << "Destructed Derived" << endl; } void printv () { cout << "This is from the Derived method call: " << v << endl; } }; int main () { Base* bp = new Derived (666 ); delete bp; return 0 ; }
执行以上代码得到如下的输出
output
可以看到,只有 Base
的析构函数被调用,这是不符合预期且危险的,在现代的编译器中,可能会给出警告:
warning 1 2 3 4 ./test.cc: In function ‘int main()’: ./test.cc:24:3: warning: deleting object of polymorphic class type ‘Base’ which has non-virtual destructor might cause undefined behavior [-Wdelete-non-virtual-dtor] 24 | delete bp; | ^~~~~~~~~
大意为多态类的基类析构函数非虚函数,可能造成未定义行为,因此我们需要虚析构函数来避免这个问题
修改后的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <bits/stdc++.h> using namespace std;class Base { public : int v = 0 ; Base (int x) : v (x) {} virtual ~Base () { cout << "Destructed Base" << endl; } Base () = default ; virtual void printv () { cout << "This is from the Base method call: " << v << endl; } }; class Derived : public Base { public : Derived (int x) : Base (x) {} Derived () = default ; ~Derived () { cout << "Destructed Derived" << endl; } void printv () { cout << "This is from the Derived method call: " << v << endl; } }; int main () { Base* bp = new Derived (666 ); delete bp; return 0 ; }
得到的输出为
output 1 2 Destructed Derived Destructed Base
可以看到基类与派生类的析构函数均被调用。
纯虚函数与抽象类
面向对象编程时,有时我们会遇到无法给出通用基类的情况,我们将无法实例化的基类称为抽象类。抽象类至少有一个纯虚函数
纯虚函数的定义
纯虚函数没有具体的定义,无法在基类被调用,因此需要派生类中定义该函数。
1 virtual 函数类型 函数名(形参表)=0 ;
这个声明末尾的 =0
并不意味着其返回值为
0,而是表示其没有被定义。
下面给出一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <bits/stdc++.h> using namespace std;class Base { public : int v = 0 ; Base (int x) : v (x) {} Base () = default ; virtual void printv () = 0 ; }; class Derived : public Base { public : Derived (int x) : Base (x) {} Derived () = default ; void printv () { cout << "This is from the Derived method call: " << v << endl; } }; int main () { Base* bp = new Derived (666 ); bp->printv (); return 0 ; }
抽象类有未定义的纯虚函数,因此无法被实例化
抽象类不能直接作为数据类型,但可以被声明成指针类型,由其指向派生类,以此实现多态
如果抽象类的派生类中有继承来的没有定义的纯虚函数,那么该派生类仍为抽象类,不能被实例化
模板
说白了就是处理泛型
函数模板与模板函数
函数模板表示泛型函数的模板,模板函数则是实例化后的具体函数。
格式如下
1 2 3 4 template <typename 参数类型>返回类型 函数名(模板形参){ ... }
下面给出一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <bits/stdc++.h> using namespace std;template <typename T>T myMax (T a, T b) { return a > b ? a : b; } template <typename T>T myMin (T a, T b) { return a > b ? b : a; } int main () { cout << myMax (1 , 2 ) << endl; cout << myMax (2.0 , 3.0 ) << endl; cout << myMin ('a' , 'b' ) << endl; cout << myMin (3.0 , 1.0 ) << endl; return 0 ; }
函数模板可与同名函数进行函数重载,优先匹配非模板函数
函数模板之间也可以重载
类模板与模板类
1 2 3 4 template <typename 类型参数>class 类名{ ... };
或
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 template <class 类型参数>class 类名{ ... }; ``` 以下面的代码为例 ```cpp #include <bits/stdc++.h> using namespace std;template <typename T>class Compare { public : T max (T a, T b) { return a > b ? a : b; } T min (T a, T b) { return a < b ? a : b; } }; int main () { Compare<int > c; cout << c.max (3 , 9 ) << " " << c.min (3 , 9 ) << endl; return 0 ; }