C++基础复习提升-virtual那些事
- 虚函数与运行多态
- 虚函数中默认参数
- 静态函数无法声明为虚函数
- 构造函数不可以声明为虚函数
- 析构函数要设为虚函数
- 基类私有虚函数的访问
- 不表现多态性的虚函数可以是内联函数
- RTTI与dynamic_cast
参考C++那些事-光城大佬的网站
上一篇C++基础复习提升-vptr与vtable那些事
下一篇C++基础复习提升-explicit那些事
虚函数与运行多态
虚函数的调用取决于指向或者引用的对象的类型,而不是指针或者引用自身的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//inline.h #ifndef _A class A { public: virtual void raise_salary(); virtual void promote(); virtual ~A(); }; class B : public A { public: void raise_salary() override; void promote() override; ~B() override; }; class C : public A { public: void raise_salary() override; void promote() override; ~C() override; }; #endif
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
28
29//main.cpp #include <iostream> #include "inline.h" using namespace std; void A::raise_salary() { cout << "A:" << 200 << endl; } void A::promote() {} A::~A() noexcept { cout << "Destruct :A" << endl; } void B::raise_salary() { cout << "B:" << 300 << endl; } void B::promote() {} B::~B() noexcept { cout << "Destruct :B" << endl; } void C::raise_salary() { cout << "C:" << 500 << endl; } void C::promote() {} C::~C() noexcept { cout << "Destruct :C" << endl; } // 打印指针数组对象的raise_salary函数 void display(A *amp[], int n) { for (int i = 0; i < n; ++i) { amp[i]->raise_salary(); } } int main() { // 创建派生类对象数组 A *amp[] = {new B, new C}; display(amp, 2); // 释放堆内存 delete amp[0]; delete amp[1]; return 0; }
虚函数中默认参数
默认参数是静态绑定的,虚函数是动态绑定的。默认参数的使用需要看指针或者引用本身的类型,而不是对象的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13// inline.h #ifndef _A class A { public: virtual void fun(int x = 10); }; class B : public A { void fun(int x = 20); }; #endif
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// main.cpp #include <iostream> #include "inline.h" using namespace std; // x在inline.h中是默认参数,值为10 A void A ::fun(int x) { cout << "Base class A fun(),x=" << x << endl; } // x在inline.h中是默认参数,值为20 B void B::fun(int x) { cout << "Children class B fun(),x=" << x << endl; } int main() { // 等同于 A *ptr = new B(); delete ptr; B b; A *ptr = &b; // 虚函数是动态绑定,多态调用B类重写A类的虚函数 // 但是因为x是默认参数,默认参数是静态绑定,所以输出的值依然是A类的x值10 ptr->fun(); return 0; }
静态函数无法声明为虚函数
静态函数不可以声明为虚函数,同时也不能被const和volatile关键字修饰
(1).static成员函数不属于任何类对象或类实例
,所以即使给此函数加上virutal也是没有任何意义
(2).虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,静态成员没有this指针,无法访问vptr
构造函数不可以声明为虚函数
构造函数不可以声明为虚函数,同时除了inline | explicit
之外,构造函数不允许使用其它任何关键字。
构造函数不能为虚函数的理由:
尽管虚函数表(vtable)在编译阶段已经建立,但指向虚函数表的指针vptr是在运行时实例化对象时才产生的,如果类含有虚函数,编译器会在构造函数中添加代码来创建vptr(注意,vptr不是c++标准,而是编译器创建的)。此时如果构造函数是虚函数,那么它需要vptr来访问vtable(所有类的虚函数都需要通过vptr访问vtable中的函数指针,通过函数指针查找虚函数),但是这个时候vptr还没有产生,因此,构造函数不可以为虚函数。
之所以使用虚函数,是因为需要在信息不全的情况下进行多态运行,而构造函数是用来初始化实例的,实例的类型必须是明确的,因此,构造函数没有必要声明为虚函数。
尽管构造函数不可以为虚函数,但是有些场景下我们确实需要"virtual copy constructor"。“虚拷贝构造函数”的说法并不严谨,其只是一个实现了对象拷贝的功能的类内函数。举例说明,若制作一个剪切板功能,复制内容为基类,但派生类可能包含文件,图片,视频等,我们只有在程序运行的时候才知道我们需要复制的具体是什么类型的数据。
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42//inline.h #ifndef _A class A { public: A(); virtual ~A(); virtual void test() = 0; // 为user中使用类A调用createObj函数埋下伏笔 static A *createObj(int id); virtual A *Clone() = 0; }; class B : public A { public: B(); B(const B &b1); ~B(); void test(); A *Clone(); }; class C : public A { public: C(); C(const C &c1); ~C(); void test(); A *Clone(); }; // User为了构造"虚拷贝"构造函数 class User { public: User(); ~User(); void Action(); private: A *pA; }; #endif
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71//main.cpp #include <iostream> #include "inline.h" using namespace std; A::A() {} A *A::createObj(int id) { if (id == 1) { return new B(); } else if (id == 2) { return new C(); } } A::~A() { cout << "A destructed" << endl; } B::B() {} B::B(const B &b1) { cout << "B copy" << endl; } B::~B() { cout << "B destructed" << endl; } void B::test() { cout << "B test" << endl; } A *B::Clone() { return new B(*this); } C::C() {} C::C(const C &c1) { cout << "C copy" << endl; } C::~C() { cout << "C destructed" << endl; } void C::test() { cout << "C test" << endl; } A *C::Clone() { return new C(*this); } // pA是A创建的对象指针,显示赋空:pA(0) User::User() : pA(0) { cout << "Enter 1 or 2:"; int input; cin >> input; while ((input != 1) && (input != 2)) { cout << "Enter 1 or 2:"; cin >> input; } // 选择的是"虚"多态的类型 pA = A::createObj(input); } User::~User() { if (pA) { delete pA; pA = 0; } } void User::Action() { // 拷贝当前对象地址 A *ptr = pA->Clone(); // 当前对象调用test() ptr->test(); delete ptr; } int main() { User *user = new User(); user->Action(); delete user; return 0; }
析构函数要设为虚函数
析构函数可以声明为虚函数,若我们需要删除一个指向派生类的基类指针时,应该把析构函数声明为虚函数。事实上,只要一个类有可能会被其它类所继承,就应该声明虚析构函数(哪怕该析构函数不执行任何操作)
基类私有虚函数的访问
私有化虚函数需要注意的一些细节,1.基类指针指向派生类对象,则调用派生类对象的函数。2.int main()必须声明为Base类的友元函数,否则main函数内无法访问私有成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//inline.h #ifndef _Base class Base { private: virtual void fun(); // 声明友元函数访问类的私有虚函数 friend int main(); public: virtual ~Base(); explicit Base(); }; class A : public Base { public: void fun(); explicit A(); ~A(); }; #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//main.cpp #include <iostream> #include "inline.h" using namespace std; void Base::fun() { cout << "Base.fun()" << endl; } Base::Base() { cout << "Base constructed" << endl; } Base::~Base() { cout << "Base destructed" << endl; } void A::fun() { cout << "A.fun()" << endl; } A::A() { cout << "A constructed" << endl; } A::~A() { cout << "A destructed" << endl; } int main() { Base *ptr = new A(); // 友元函数中访问类的私有成员 ptr->fun(); delete ptr; return 0; }
不表现多态性的虚函数可以是内联函数
通常类成员函数都会被编译器考虑是否进行内联,但通过基类指针或者引用调用的虚函数必定不能被内联,当然实体对象调用虚函数或者静态调用时可以被内联,虚析构函数的静态调用也一定会被内联展开。
1.虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性时不能内联。
2.内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
3.inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// inline.h #ifndef _Base class Base { public: // 函数声明不能使用inline virtual void fun(); virtual ~Base(); explicit Base(); }; class A : public Base { public: explicit A(); ~A(); void fun(); }; #endif
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
28
29
30
31
32// main.cpp #include <iostream> #include "inline.h" using namespace std; inline void Base::fun() { cout << "Base explicit inline func" << endl; } Base::Base() {} Base::~Base() {} inline void A::fun() { cout << "A explicit inline func" << endl; } A::A() {} A::~A() {} int main() { // 通过Base类的具体对象调用虚函数,在编译器就能确定由哪个对象调用 // 因此可以内联 Base base; base.fun(); // 通过指针或者引用的多态方式调用虚函数,需要在运行期才能确定 // 所以不能内联 Base *ptr = new A(); ptr->fun(); // 基类Base存在虚析构,释放堆内存时 // 会先销毁派生类A对象再销毁基类对象,防止内存泄漏 delete ptr; return 0; }
RTTI与dynamic_cast
RTTI(RunTime Type Identification)运行时类型信息程序能够使用基类的指针或引用来检查这些指针或者引用所指的对象的实际派生类型
在运行时查询一个对象是否能作为某种多态类型使用,C++提供了dynamic_cast函数用于动态转型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//inline.h #ifndef _Base class Base { private: virtual void fun(); }; class A : public Base { }; class D { }; #endif
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
28
29
30
31
32
33
34
35
36//main.cpp #include <iostream> #include "inline.h" using namespace std; void Base::fun() {} int main() { // 向上转型 Base *base = new A(); // 正确 // Base &obj = *(new Base); obj = *(new A); // error // obj = *(new D); Base &obj = *base; // 向下转型 A *a = dynamic_cast<A *>(base); // A *a(0); if (a != nullptr) { cout << "works" << endl; } else cout << "cannot cast Base* to A*" << endl; try { A &a_obj = dynamic_cast<A &>(obj); cout << "works" << endl; } catch (bad_cast bc) { cout << bc.what() << endl; } delete a; return 0; }
最后
以上就是个性战斗机最近收集整理的关于C++基础复习提升-virtual那些事虚函数与运行多态虚函数中默认参数静态函数无法声明为虚函数构造函数不可以声明为虚函数析构函数要设为虚函数基类私有虚函数的访问不表现多态性的虚函数可以是内联函数RTTI与dynamic_cast的全部内容,更多相关C++基础复习提升-virtual那些事虚函数与运行多态虚函数中默认参数静态函数无法声明为虚函数构造函数不可以声明为虚函数析构函数要设为虚函数基类私有虚函数内容请搜索靠谱客的其他文章。
发表评论 取消回复