大纲 C++ 面向对象浅析 继承中的构造顺序、访问权限、函数隐藏 继承中的构造顺序 在 C++ 的继承体系中构造函数的调用顺序先构造基类,再构造派生类,否则派生类无法安全使用基类成员 析构函数的调用顺序先析构派生类,再析构基类,否则派生类的析构函数可能访问已经被销毁的基类成员 记忆口诀:先构造后析构,后构造先析构 继承与组合混搭情况下的构造与析构顺序
继承与组合对象混搭使用的情况下,构造函数与析构函数的调用原则如下: (1) 构造函数的调用顺序:先构造父类,再构造成员变量,最后构造自身。 (2) 析构函数的调用顺序:先析构自身,再析构成员变量,最后析构父类。 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 #include <iostream> using namespace std;class Base {public : Base () { cout << "Base constructor" << endl; } ~Base () { cout << "Base destructor" << endl; } }; class Derived : public Base {public : Derived () { cout << "Derived constructor" << endl; } ~Derived () { cout << "Derived destructor" << endl; } }; int main () { Derived d; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 Base constructor Derived constructor Derived destructor Base destructor
继承中的访问权限 继承中的函数隐藏 什么是函数隐藏
在 C++ 的继承体系中,如果子类声明了一个与父类同名的成员函数(无论参数列表是否相同),那么在子类作用域内,父类中所有同名函数都会被隐藏,无法通过子类普通对象调用方式访问。这种现象称为函数隐藏(Function Hiding),也称为 “函数遮蔽”。
造成函数隐藏的本质原因是:在 C++ 编译期的名字查找(Name Lookup)过程中,如果在派生类作用域中找到了某个函数名,编译器将停止向基类作用域继续查找该名称,从而导致基类中所有同名函数(包括不同参数列表的重载函数)都会被隐藏。
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 #include <iostream> using namespace std;class Base {public : void func () { cout << "Base::func()" << endl; } void func (int x) { cout << "Base::func(int): " << x << endl; } void func (double d) { cout << "Base::func(double): " << d << endl; } }; class Derived : public Base {public : void func (double d) { cout << "Derived::func(double): " << d << endl; } }; int main () { Derived d; d.func (3.14 ); d.func (); d.func (10 ); return 0 ; }
上述代码为什么会报错?
因为只要子类 Derived 声明了 func 这个成员函数名称,那么 Base::func() 和 Base::func(int) 全部都会被隐藏(不是重写,是隐藏)。 函数隐藏的本质
C++ 编译器的函数查找规则是:先在子类作用域查找 如果找到同名函数 则停止查找父类作用域 不管参数是否匹配 这叫做:名字查找(Name Lookup)规则 函数隐藏的特征
只要基类与派生类中有相同函数名,那么就会发生函数隐藏 函数隐藏与函数参数列表是否相同无关 函数隐藏与函数是否被 virtual 修饰无关 函数隐藏发生在 C++ 编译期名字查找阶段 函数隐藏不是多态行为 函数隐藏解决方案一
使用 using 关键字重新引入父类函数(推荐),也就是让父类同名函数在子类中以重载的方式来使用 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 #include <iostream> using namespace std;class Base {public : void func () { cout << "Base::func()" << endl; } void func (int x) { cout << "Base::func(int): " << x << endl; } void func (double d) { cout << "Base::func(double): " << d << endl; } }; class Derived : public Base {public : using Base::func; void func (double d) { func (); func (20 ); func (2.63 ); cout << "Derived::func(double): " << d << endl; } }; int main () { Derived d; d.func (3.14 ); d.func (); d.func (10 ); return 0 ; }
函数隐藏解决方案二
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 #include <iostream> using namespace std;class Base {public : void func () { cout << "Base::func()" << endl; } void func (int x) { cout << "Base::func(int): " << x << endl; } void func (double d) { cout << "Base::func(double): " << d << endl; } }; class Derived : public Base {public : void func (double d) { Base::func (); Base::func (20 ); Base::func (6.19 ); cout << "Derived::func(double): " << d << endl; } }; int main () { Derived d; d.func (3.14 ); d.Base::func (); d.Base::func (10 ); d.Base::func (3.14 ); return 0 ; }
函数隐藏 VS 函数重写
概念 条件 是否需要 virtual 修饰 是否隐藏 重写(override) 函数签名完全相同 必须 不隐藏 隐藏(hiding) 只要名字相同 不需要 会隐藏
函数隐藏高频面试题
1 2 3 4 5 6 7 8 9 10 11 12 13 class Base {public : virtual void show (int x) { cout << "Base show(int)" << endl; } }; class Derived : public Base {public : void show (double x) { cout << "Derived show(double)" << endl; } };
上述代码:不是函数重写(因为函数签名不同),也不是函数重载(因为同名函数不在同一个类),而是函数隐藏 virtual 关键字不会生效,多态不会发生 继承中的构造函数继承、多继承、虚继承 构造函数继承 关键点
通过 using A::A;,派生类可以继承直接基类的有参构造函数(主要是非特殊的有参构造函数),但基类的默认构造函数、默认拷贝构造函数、默认移动构造函数不会被继承,它们仍由编译器按规则为派生类自动生成(或被删除)。 这样可以避免重复编写构造函数、提高复用性,但如果派生类中定义了相同参数列表的构造函数,会覆盖(隐藏)从基类继承而来的构造函数。 基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class A {public : A (int i, int j) {} A (int i, int j, int k) {} }; class B : public A {public : using A::A; }; int main () { B b1 (1 , 2 ) ; B b2 (1 , 2 , 3 ) ; return 0 ; }
using A::A; 使派生类 B 继承基类 A 的全部有参构造函数,除了基类的默认构造函数、默认拷贝构造函数、默认移动构造函数(如果存在)。编译器会为 B 自动生成与 A 对应的有参构造函数。 等价效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class A {public : A (int i, int j) {} A (int i, int j, int k) {} }; class B : A { B (int i, int j) : A (i, j) {} B (int i, int j, int k) : A (i, j, k) {} }; int main () { B b1 (1 , 2 ) ; B b2 (1 , 2 , 3 ) ; return 0 ; }
本质上相当于为每个父类构造函数在子类中生成一个 “转发构造函数”。 参数规则限制
派生类继承的构造函数参数列表必须与基类一致,不能减少或增加参数。 但可以在派生类中额外定义自己的构造函数。 默认构造函数情况
1 2 3 4 5 6 7 8 9 10 11 12 13 class A { A (int i, int j, int k) { } }; class B : A { using A::A; }; int main () { return 0 ; }
如果 A(基类)没有默认构造函数,且 B(派生类)也没有定义默认构造函数,那么编译器不会为 B 自动生成默认构造函数,反之亦然。 继承构造函数的限制
派生类只能继承直接基类的有参构造函数,且不包括基类的默认构造函数、默认拷贝构造函数、默认移动构造函数(如果存在)。 如果派生类中定义了相同参数列表的构造函数,那么派生类会覆盖(隐藏)从基类继承来的构造函数。 多重继承 多重继承的使用
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #include <iostream> using namespace std;class Grand {public : Grand (int i) : m_value_grand (i) { cout << "Grand(int)" << endl; } virtual ~Grand () { cout << "~Grand()" << endl; } void info () { cout << m_value_grand << endl; } public : int m_value_grand; }; class A : public Grand {public : A (int i) : Grand (i), m_value_a (i) { cout << "A(int)" << endl; } virtual ~A () { cout << "~A()" << endl; } void info () { cout << m_value_a << endl; } public : int m_value_a; }; class B {public : B (int i) : m_value_b (i) { cout << "B(int)" << endl; } virtual ~B () { cout << "~B()" << endl; } void info () { cout << m_value_b << endl; } public : int m_value_b; }; class C : public A, public B {public : C (int i, int j, int k) : A (i), B (j), m_value_c (k) { cout << "C(int, int, int)" << endl; } virtual ~C () { cout << "~C()" << endl; } void infoA () { A::info (); } void infoB () { B::info (); } public : int m_value_c; }; int main () { C c1 (10 , 20 , 30 ) ; c1.A::info (); c1.infoA (); c1.infoB (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 11 Grand(int) A(int) B(int) C(int, int, int) 10 10 20 ~C() ~B() ~A() ~Grand()
派生类的构造函数与析构函数
构造一个派生类对象时,会自动调用其所有基类的构造函数 派生类的构造函数必须负责: 基类的初始化优先于派生类,顺序如下:(1) 先调用基类构造函数 (2) 再调用派生类构造函数 如果存在多继承:基类的构造顺序按照继承列表中的声明顺序执行(与初始化列表顺序无关) 比如:class C : public A, public B { },这里会根据继承列表先构造 A,然后再构造 B 析构函数执行顺序与构造函数相反:(1) 先执行派生类析构函数 (2) 再执行基类析构函数 多继承中的静态变量 关键点
static 变量属于类,在整个继承体系中只有一份共享实例;父类修改后,子类访问到的一定是修改后的值,不存在子类单独拥有一份 static 副本的情况。
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 72 73 74 75 76 77 78 79 80 81 #include <iostream> using namespace std;class Grand {public : Grand (int i) : m_value_grand (i) { } virtual ~Grand () { } void info () { cout << m_value_grand << endl; } public : int m_value_grand; static int m_value_static; }; int Grand::m_value_static = 2 ;class A : public Grand {public : A (int i) : Grand (i), m_value_a (i) { } virtual ~A () { } void info () { cout << m_value_a << endl; } public : int m_value_a; }; class B {public : B (int i) : m_value_b (i) { } virtual ~B () { } void info () { cout << m_value_b << endl; } public : int m_value_b; }; class C : public A, public B {public : C (int i, int j, int k) : A (i), B (j), m_value_c (k) { } virtual ~C () { } void infoA () { cout << m_value_c << endl; } public : int m_value_c; }; int main () { A::m_value_static = 15 ; cout << A::m_value_static << endl; cout << C::m_value_static << endl; return 0 ; }
程序运行输出的结果如下:
这里还有一特殊种情况,子类可以 “隐藏” 父类的 static 变量(但不推荐)
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <iostream> using namespace std;class Grand {public : Grand (int i) : m_value_grand (i) { } virtual ~Grand () { } void info () { cout << m_value_grand << endl; } public : int m_value_grand; static int m_value_static; }; int Grand::m_value_static = 2 ;class A : public Grand {public : A (int i) : Grand (i), m_value_a (i) { } virtual ~A () { } void info () { cout << m_value_a << endl; } public : int m_value_a; }; class B {public : B (int i) : m_value_b (i) { } virtual ~B () { } void info () { cout << m_value_b << endl; } public : int m_value_b; }; class C : public A, public B {public : C (int i, int j, int k) : A (i), B (j), m_value_c (k) { } virtual ~C () { } void infoA () { cout << m_value_c << endl; } public : int m_value_c; static int m_value_static; }; int C::m_value_static = 4 ;int main () { A::m_value_static = 15 ; cout << A::m_value_static << endl; cout << C::m_value_static << endl; C::m_value_static = 30 ; cout << A::m_value_static << endl; cout << C::m_value_static << endl; return 0 ; }
程序运行输出的结果如下:
从多个基类继承构造函数 关键点
如果派生类通过 using 关键字继承了多个基类中签名相同(参数列表相同)的构造函数,会产生二义性并导致编译错误;此时需要在派生类中显式定义对应的构造函数来消除歧义;需要注意的是,构造函数不能被覆盖(override),这里只是重新定义并明确调用哪个基类的构造函数。
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 #include <iostream> using namespace std;class A {public : A (int i) { cout << "A()" << endl; } ~A () { cout << "~A()" << endl; } }; class B {public : B (int i) { cout << "B()" << endl; } ~B () { cout << "~B()" << endl; } }; class C : public A, public B {public : using A::A; using B::B; C (int i) : A (i), B (i) { } }; int main () { C c1 (10 ) ; return 0 ; }
程序运行输出的结果如下:
虚继承 虚继承的概念 主要用途:
虚继承主要用于解决菱形继承中的数据冗余和二义性问题,使基类在继承体系中只保留一份实例 定义方式:
在继承时,使用 virtual 关键字,比如:class A : virtual public Base {}; 核心特性:
多个子类共享同一份基类对象 最终在派生类中,只会存在一份基类实例 不使用虚继承时:
基类被重复继承,导致派生类中出现多份基类数据 当派生类访问基类成员时,会产生二义性 使用虚继承后:
消除数据冗余,在派生类中只会存在一份基类实例 访问基类成员不再出现二义性 构造规则:
虚基类由最底层的派生类负责初始化 中间类即使写了虚基类的构造调用,也会被忽略 底层原理:
虚继承的底层是靠虚基类指针(vbptr)和虚基类表(vbtable)来实现。 适用场景:
虚继承只适用于有共同基类(公共基类)的多继承场景(比如菱形继承),如图所示 不适用场景:
对于 V 字形的多继承场景,虚继承是没办法解决二义性问题的,如图所示 总结
在 C++ 中,虚继承的声明需要使用关键字 virtual,被虚继承的类通常称作为虚基类。 如果一个派生类从多个基类继承,而这些基类又有一个共同的基类(公共基类),则在对该基类中声明的成员进行访问时,可能会产生二义性,需要使用虚继承来解决二义性问题。 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象。要使这个公共基类在派生类中只产生一个子对象,必须对这个基类的继承声明为虚继承,使这个基类成为 虚基类。 虚继承的使用 菱形继承存在的二义性问题
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #include <iostream> using namespace std;class Grand {public : Grand (int i) : m_value_grand (i) { cout << "Grand(int)" << endl; } virtual ~Grand () { cout << "~Grand()" << endl; } void info () { cout << m_value_grand << endl; } public : int m_value_grand; }; class A : public Grand {public : A (int i) : Grand (i), m_value_a (i) { cout << "A(int)" << endl; } virtual ~A () { cout << "~A()" << endl; } void info () { cout << m_value_a << endl; } public : int m_value_a; }; class A2 : public Grand {public : A2 (int i) : Grand (i), m_value_a2 (i) { cout << "A2(int)" << endl; } virtual ~A2 () { cout << "~A2()" << endl; } void info () { cout << m_value_a2 << endl; } public : int m_value_a2; }; class B {public : B (int i) : m_value_b (i) { cout << "B(int)" << endl; } virtual ~B () { cout << "~B()" << endl; } void info () { cout << m_value_b << endl; } public : int m_value_b; }; class C : public A, public A2, public B {public : C (int i, int j, int k) : A (i), A2 (i), B (j), m_value_c (k) { cout << "C(int, int, int)" << endl; } virtual ~C () { cout << "~C()" << endl; } void infoA () { cout << m_value_c << endl; } public : int m_value_c; }; int main () { C c1 (10 , 20 , 30 ) ; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 Grand(int) A(int) Grand(int) A2(int) B(int) C(int, int, int) ~C() ~B() ~A2() ~Grand() ~A() ~Grand()
使用虚继承来解决菱形继承的二义性问题
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include <iostream> using namespace std;class Grand {public : Grand (int i) : m_value_grand (i) { cout << "Grand(int)" << endl; } virtual ~Grand () { cout << "~Grand()" << endl; } void info () { cout << m_value_grand << endl; } public : int m_value_grand; }; class A : virtual Grand {public : A (int i) : Grand (i), m_value_a (i) { cout << "A(int)" << endl; } virtual ~A () { cout << "~A()" << endl; } void info () { cout << m_value_a << endl; } public : int m_value_a; }; class A2 : virtual public Grand {public : A2 (int i) : Grand (i), m_value_a2 (i) { cout << "A2(int)" << endl; } virtual ~A2 () { cout << "~A2()" << endl; } void info () { cout << m_value_a2 << endl; } public : int m_value_a2; }; class B {public : B (int i) : m_value_b (i) { cout << "B(int)" << endl; } virtual ~B () { cout << "~B()" << endl; } void info () { cout << m_value_b << endl; } public : int m_value_b; }; class C : public A, public A2, public B {public : C (int i, int j, int k) : Grand (i), A (i), A2 (i), B (j), m_value_c (k) { cout << "C(int, int, int)" << endl; } virtual ~C () { cout << "~C()" << endl; } void infoA () { cout << m_value_c << endl; } public : int m_value_c; }; int main () { C c1 (10 , 20 , 30 ) ; c1.m_value_grand = 20 ; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 Grand(int) A(int) A2(int) B(int) C(int, int, int) ~C() ~B() ~A2() ~A() ~Grand()
虚继承的初始化规则 在继承体系中,如果存在虚基类(如 Grand):
虚基类由最底层的派生类负责初始化 中间层(如 A、A2)即使写了对 Grand 的构造调用,也不会真正生效 换句话说:
初始化顺序规则:
(1) 先初始化所有虚基类 (2) 再按照继承列表顺序,初始化非虚基类 (3) 最后初始化当前类的自身成员 总结
虚基类只初始化一次,并且由最底层的派生类统一负责初始化。简而言之,虚基类的初始化顺序优先于所有普通基类。 友元函数、友元类、友元成员函数 特别注意
友元关系不能被继承;友元关系是单向的;友元关系没有传递性。
友元类的使用 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 #include <iostream> #include <memory> #include <string> using namespace std;class Human {public : Human () { cout << "Human::Human()" << endl; } Human (const string& name, int age) : m_Name (name), m_Age (age) { cout << "Human::Human(string, int)" << endl; } virtual ~Human () { cout << "Human::~Human()" << endl; } void eat () const { cout << m_Name << "Human eat food" << endl; } public : friend class Game ; private : int m_Age; string m_Name; }; class Game {public : explicit Game (shared_ptr<Human> h) : m_Human(std::move(h)) { cout << "Game::Game()" << endl; } ~Game () { cout << "Game::~Game()" << endl; } void getHuman () const { if (m_Human) { cout << "name = " << m_Human->m_Name << ", age = " << m_Human->m_Age << endl; } } private : shared_ptr<Human> m_Human; }; int main () { shared_ptr<Human> human = make_shared<Human>("Jim" , 28 ); Game game (human) ; game.getHuman (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 Human::Human(string, int) Game::Game() name = Jim, age = 28 Game::~Game() Human::~Human()
友元函数的使用 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 #include <iostream> #include <memory> #include <string> using namespace std;class Human {public : Human () { cout << "Human::Human()" << endl; } Human (const string& name, int age) : m_Name (name), m_Age (age) { cout << "Human::Human(string, int)" << endl; } virtual ~Human () { cout << "Human::~Human()" << endl; } void eat () const { cout << m_Name << "Human eat food" << endl; } public : friend void show (const Human& human) ; private : int m_Age; string m_Name; }; void show (const Human& human) { cout << "name = " << human.m_Name << ", age = " << human.m_Age << endl; } int main () { Human human ("Jim" , 18 ) ; show (human); return 0 ; }
程序运行输出的结果如下:
1 2 3 Human::Human(string, int) name = Jim, age = 18 Human::~Human()
友元成员函数的使用 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 #include <iostream> #include <memory> #include <string> using namespace std;class Human ;class Game {public : Game (shared_ptr<Human> h); ~Game (); void getHuman () const ; private : shared_ptr<Human> m_Human; }; class Human {public : Human (const string& name, int age) : m_Name (name), m_Age (age) { cout << "Human::Human(string, int)" << endl; } virtual ~Human () { cout << "Human::~Human()" << endl; } private : int m_Age; string m_Name; friend void Game::getHuman () const ; }; Game::Game (shared_ptr<Human> h) : m_Human (h) { cout << "Game::Game()" << endl; } Game::~Game () { cout << "Game::~Game()" << endl; } void Game::getHuman () const { if (m_Human) { cout << "name = " << m_Human->m_Name << ", age = " << m_Human->m_Age << endl; } } int main () { shared_ptr<Human> human = make_shared<Human>("Jim" , 28 ); Game game (human) ; game.getHuman (); }
程序运行输出的结果如下:
1 2 3 4 5 Human::Human(string, int) Game::Game() name = Jim, age = 28 Game::~Game() Human::~Human()
关键点 Game 类必须先完整声明成员函数getHuman() 必须先声明,再在 Human 里声明为友元成员函数 前向声明不能直接友元成员函数如果 Human 还没见过 Game 的完整声明,就无法指定友元成员函数 友元成员函数可以访问 Human 私有成员,无需把整个 Game 类声明为友元 特别注意
在 C++ 中,有一个关键限制:只能把全局函数、其他类的成员函数、或者模板函数声明为友元;但是前向声明的类的成员函数不能直接作为友元,除非类完整声明后才可用。
RTTI、dynamic_cast、typeid、虚函数表 RTTI 的核心概念 RTTI 的概述
RTTI(Run Time Type Identification,运行时类型识别)是 C++ 提供的一种机制,用于在程序运行期间确定对象的实际类型。 当通过基类指针或引用操作派生类对象时,可以借助 RTTI 查询对象的真实派生类型。 RTTI 的实现机制
主要通过以下机制实现:dynamic_cast —— 安全地进行向下类型转换(基类 → 派生类)typeid —— 获取对象的实际类型信息 RTTI 的注意事项:
RTTI 仅对包含至少一个虚函数的多态类型(即必须至少有一个虚函数的基类)有效。 必须通过基类的指针或引用访问对象,才能获取运行时的真实类型。 dynamic_cast 的使用 dynamic_cast 的概述
dynamic_cast 是动态类型转换,用于如父类和子类之间的多态类型转换。dynamic_cast 在运行期会基于 RTTI 进行类型检查,但只能用于多态类型(即继承体系中必须存在虚函数 ),能保证下行转换的安全性。dynamic_cast 可以用于类层次结构中的上行转换和下行转换,但是不支持基本数据类型的转换。在类层次结构中进行上行转换(将派生类的指针或者引用转换成基类表示)时,dynamic_cast 与 static_cast 的效果一样。 在类层次结构中进行下行转换(将基类的指针或者引用转换成派生类表示)时,dynamic_cast 具有运行时类型检查的功能,比 static_cast 更安全。 对于指针,如果 dynamic_cast 转换失败,会返回一个空指针(nullptr)。对于引用,如果 dynamic_cast 转换失败,会抛出 std::bad_cast 异常。 特别注意
在 C++ 的四种显式类型转换中,static_cast 和 const_cast 会在编译期进行类型检查;reinterpret_cast 仅在编译期会做最基本的语法检查,不进行类型安全检查;dynamic_cast 会在运行期基于 RTTI 机制进行类型检查(仅适用于多态类型)。
dynamic_cast 的使用
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 72 73 74 75 #include <iostream> #include <typeinfo> using namespace std;class Human {public : Human () { cout << "Human::Human()" << endl; } Human (const string& name, int age) : m_Name (name), m_Age (age) { cout << "Human::Human(string, int)" << endl; } virtual ~Human () { cout << "Human::~Human()" << endl; } virtual void sleep () { cout << "Human::sleep()" << endl; } protected : int m_Age; string m_Name; }; class Men : public Human {public : Men () { cout << "Men::Men()" << endl; } Men (const string& name, int age) { this ->m_Age = age; this ->m_Name = name; cout << "Men::Men(string, int)" << endl; } ~Men () { cout << "Men::~Men()" << endl; } virtual void sleep () override { cout << "Men::sleep()" << endl; } void eat () { cout << "Men::eat()" << endl; } }; int main () { Human* human = new Men ("Jim" , 18 ); human->sleep (); Men* men = dynamic_cast <Men*>(human); if (men != nullptr ) { men->eat (); } delete human; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 Human::Human() Men::Men(string, int) Men::sleep() Men::eat() Men::~Men() Human::~Human()
typeid、type_info 的使用 typeid 的介绍
typeid 的概述
typeid 用于获取类型信息,返回值类型为 const std::type_info&,即返回一个 std::type_info 常量对象的引用。对于多态类型,通过基类指针或引用可在运行时获取对象的真实类型。 typeid 的用法
语法一:typeid(类型) 语法二:typeid(表达式) typeid 的使用情况一
情况一:基础类型typeid(表达式) 在编译期确定类型不会发生运行时类型识别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std;int main () { int a = 10 ; char b[10 ] = {5 , 1 }; float c = 0.32 ; double d = 3.14 ; string e = "abc" ; cout << typeid (a).name () << endl; cout << typeid (b).name () << endl; cout << typeid (c).name () << endl; cout << typeid (d).name () << endl; cout << typeid (e).name () << endl; }
typeid 的使用情况二
情况二:非多态类型(没有虚函数)typeid(表达式) 在编译期确定类型不会发生运行时类型识别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std;class A {}; int main () { A a; cout << typeid (a).name () << endl; if (typeid (a) == typeid (A)) { cout << "a 是 A 类型" << endl; } else { cout << "a 是其他类型" << endl; } return 0 ; }
typeid 的使用情况三
情况三:多态类型(存在虚函数)如果:基类中存在至少一个虚函数 通过基类指针或引用访问派生类对象 那么:typeid(表达式) 会在运行时获取对象的真实类型依赖 RTTI 机制(运行时类型识别机制) 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 #include <iostream> using namespace std;class Base {public : virtual ~Base () { } }; class Derived : public Base {}; int main () { Base* p = new Derived (); cout << typeid (*p).name () << endl; if (typeid (*p) == typeid (Base)) { std::cout << "指针 p 指向 Base 类型" << endl; } else if (typeid (*p) == typeid (Derived)) { std::cout << "指针 p 指向 Derived 类型" << endl; } else { std::cout << "指针 p 指向未知类型" << endl; } return 0 ; }
特别注意
比较 typeid 返回的类型时,不要使用 std::type_info::name() 进行字符串比较,因为 std::type_info::name() 返回的是实现相关的字符串(可能经过修饰),不同编译器返回的结果不相同,不具备可移植性,不能作为可靠的类型判断依据。应该使用 std::type_info::operator== 进行类型比较,例如 typeid(*p) == typeid(Derived)。如果目的是判断对象是否属于某个派生类并进行安全的向下类型转换,则更推荐使用 dynamic_cast,如 if (Derived* d = dynamic_cast<Derived*>(p)) { },这样既能判断类型,又能安全使用派生类。
RTTI 与虚函数表的关系 什么是虚函数表
虚函数表的概述
在 C++ 中,如果一个类中包含至少一个虚函数,编译器通常会为该类生成一个虚函数表(vtable)。 虚函数表的本质
虚函数表本质上是一个函数指针数组 表中的每一项都是一个函数入口地址 每个包含虚函数的类,通常都会对应一个虚函数表 每个对象内部会保存一个指向虚函数表的指针(通常称为 vptr) 虚函数表的结构
1 2 3 4 5 6 7 8 对象内存布局: +-------------------+ | vptr -----------+----> 虚函数表(vtable) +-------------------+ | | 成员变量1 | |--> 虚函数1地址 | 成员变量2 | |--> 虚函数2地址 +-------------------+ |--> ...
虚函数表中的内容
虚函数表中通常包含:各个虚函数的入口地址 析构函数的地址 与 RTTI 相关的信息 RTTI 与虚函数表的关系
当类是多态类型(包含虚函数)时:
编译器不仅会生成虚函数表 还会为该类生成一个 type_info 对象 该对象记录类的类型信息 虚函数表中通常会包含一个指向该 type_info 对象的指针 因此:
RTTI 机制通常依赖虚函数表来实现运行时类型识别 当调用:
如果 p 是基类指针且类为多态类型:
程序会通过对象中的虚函数表指针(vptr) 找到虚函数表 再获取与之关联的 type_info 从而得到对象的真实运行时类型 关键结论
没有虚函数 → 没有多态 → 不会产生完整的 RTTI 运行时行为 必须通过基类指针或引用访问对象,RTTI 才能识别真实派生类型 RTTI 的底层实现通常依赖虚函数表 总结
在 C++ 中,只要类包含虚函数,编译器就会生成虚函数表;RTTI 机制通常依赖虚函数表中的类型信息指针来实现运行时类型识别。