C++学习细节回顾(汇总三)
一.多态概念
同样是动物叫的⼀个⾏为(函数),传猫对象过去,就是”(>ω<)喵“,传狗对象过去,就是"汪汪"。
1.根据对象不同类型,调用不同函数,这就叫做运行时多态(动态多态)
2.编译时多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态
———————————————————————————————
二.实现多态还有两个必须重要条件:
• 必须是通过基类的指针或者引⽤调⽤虚函数
• 被调⽤的函数必须是虚函数,并且完成了虚函数重写/覆盖。
原因分析:多态实现必须条件分析
———————————————————————————————
三.重写/覆盖
1.重写意味这,通过对父类成员虚函数的声明,然后重新写函数实现(声明采用基类,实现采用自己写的)
2.函数名,参数,返回值相同
3.由于派生类重写时的声明采用的是,基类成员虚函数的声明因此,在基类中已经写过virtual在成员函数前,派生类成员函数不用再写virtual,不过建议写上
———————————————————————————————
四.静态/动态多态
1.编译时多态(静态多态),函数重载和函数模板,编译时传入参数匹配,生成函数
2.运行时多态(动态多态),编译阶段无法知道,实际调用哪个版本的函数是在运行时根据对象的实际类型决定的
———————————————————————————————
五.析构函数的重写
基类的析构函数为虚函数,此时派⽣类析构函数只要定义(看上面重写分析),⽆论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写。
delete的原理delete详解剖析
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
class A
{public:virtual ~A(){cout << "~A()" << endl;}
};
class B : public A
{public:~B(){cout << "~B()->delete:"<<_p<< endl;delete _p;}
protected:
int* _p = new int[10];
};
// 只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能
//构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。
int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}
上面的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调⽤的A的析构函数,没有调用B的析构函数,就会导致内存泄漏问题,因为~B()中在释放资源。
注意:这个问题⾯试中经常考察,⼤家⼀定要结合类似下⾯的样例才能讲清楚,为什么基类中的析构函数建议设计为虚函数。
———————————————————————————————
六.重载 重写/覆盖 隐藏 三个概念的对比辨析
———————————————————————————————
七.纯虚函数和抽象类
在虚函数声明时在函数签名末尾添加 = 0
,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被派⽣类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。纯虚函数某种程度上强制了派⽣类重写虚函数,因为不重写实例化不出对象
抽象类就类似,car和各种具体车的品牌,car作为抽象类 ,Benz和BMW作为派生类
———————————————————————————————
八.多态底层原理 多态实现必须条件分析
对不满⾜多态条件(指针或者引⽤+调⽤虚函数)的函数调⽤是在编译时绑定,也就是编译时确定调⽤函数的地址,叫做静态绑定。
• 满⾜多态条件的函数调⽤是在运⾏时绑定,也就是在运⾏时到指向对象的虚函数表中找到调⽤函数的地址,也就做动态绑定。
虚函数表
• 基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共⽤同⼀张虚表,不同类型的对象各⾃有独⽴的虚表,所以基类和派⽣类有各⾃独⽴的虚表。
• 派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴的。
• 派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。
• 派⽣类的虚函数表中包含,(1)基类的虚函数地址,(2)派⽣类重写的虚函数地址完成覆盖,派⽣类⾃⼰的虚函数地址三个部分。
• 虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后⾯放了⼀个0x00000000标记。(这个C++并没有进⾏规定,各个编译器⾃⾏定义的,vs系列编译器会再后⾯放个0x00000000标记,g++系列编译不会放)
• 虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址⼜存到了虚表中。
• 虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,我们写下⾯的代码可以对⽐验证⼀下。vs下是存在代码段(常量区)
注意:同类型对象共有同一张虚函数表
———————————————————————————————
八.指针数组/数组指针
1.指针数组:void (*pf)()
2.数组指针:int (*pa)[10]
——————————————————————————————
九.
派生类对象析构时会自动在结束时自动增加析构基类部分,目的是保证先子后父
——————————————————————————————
继承
一.让一个类不能被继承
1.构造函数私有化(派生类在构造时调用不到基类构造函数)
2.关键字final class Base final{ };
二.
1.友元关系不能继承
2.虚继承(虚继承(Virtual Inheritance)是C++中处理多重继承时避免二义性问题的一种机制,特别是当一个类从多个基类继承,而这些基类又有一个共同的基类时。通过使用虚继承,可以确保共同基类的成员在派生类中只有一个副本,从而避免了重复继承带来的数据冗余和访问冲突.)
三.继承语法
四.切片特性
public继承的派⽣类对象 可以赋值给 基类的指针 / 基类的引⽤。这⾥有个形象的说法叫切⽚或者切割。寓意把派⽣类中基类那部分切出来,基类指针或引⽤指向的是派⽣类中切出来的基类那部分。
五.隐藏
- 在继承体系中基类和派⽣类都有独⽴的作⽤域。
- 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系⾥⾯最好不要定义同名的成员。