一、多态的概念
摘要:多态:一个接口,多种方法,运行时决定调用哪个。
多态是面向对象编程领域的核心概念(封装wrap(类) 继承inheritance(代码重用) 多态polymorphism(函数重用-适应对象))
虚函数实现多态
虚函数的限制:成员函数(非静态,非构造)
二、重写(覆盖)、重载、隐藏(重定义,理解为不同域的重载)
重写--覆盖(override):重写虚函数,名字、参数、返回值都必须相同(虚函数、不同作用域,其他相同,相当于覆盖继承来的虚成员函数的函数体内容)
重载(overload):成员函数重载(同域同名)
重定义-隐藏:(不同域,同名),相当于子类有两个该同名函数,默认访问子类的,可以加父类作用域访问符来访问继承的函数
参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(因为不同域不能重载,所以称为隐藏)
参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意与覆盖的区别)(基类有virtual就是覆盖了)
总结: | |
同域,同名 | 重载 |
虚函数,不同域,同名同参同返回值 | 重写 |
虚函数,不同域,同名 | 隐藏 |
非虚函数,不同域,同名 | 隐藏(理解为不同域的重载) |
#include <iostream>
using namespace std;
class Base{
public:
Base(int val) : val(val)
{
cout << __func__ << ":" << __LINE__ << endl;
}
~Base()
{
cout << __func__ << ":" << __LINE__ << endl;
}
public:
virtual void prnmsg();//---1 虚函数,且子类同名函数也自动为虚函数
void prnmsg(int i); //---2 1和2是重载(同作用域同名)
private:
int val;
};
void Base::prnmsg() //函数定义不需要virtual关键字
{
cout << __func__ << ":" << __LINE__ << endl;
}
void Base::prnmsg(int i)
{
cout << __func__ << ":" << __LINE__ << ":" << i<< endl;
}
class Inherit:public Base{ //继承 is-a关系
public:
Inherit(int val) : myval(val),Base(val)
{
cout << __func__ << ":" << __LINE__ << endl;
}
~Inherit()
{
cout << __func__ << ":" << __LINE__ << endl;
}
void prnmsg() //---3重写/覆盖(针对虚函数,不同作用域,同名同参同返回值)
{
cout << __func__ << ":" << __LINE__ << endl;
}
void prnmsg(int i) //---4隐藏/重定义(不同作用域,同名,理解为不同域的重载)
{
cout << __func__ << ":" << __LINE__ << ":" << i<< endl;
}
private:
int myval;
};
//静态链接函数
void test(Base &obj){
obj.prnmsg(); //如果obj是Base对象,则照常静态链接调用,否则就动态链接,调用相应函数
obj.prnmsg(123);//非虚函数,不论对象,都调用Base???
}
int main()
{
Base obja(123);
Inherit objb(666);
test(obja); //正常调用Base,有参和无参
test(objb);//Inherit -> Base //包含关系?调用1次重写的虚函数,一次Base(有参)
objb.prnmsg(888); //子类隐藏函数(有参)
objb.Base::prnmsg(666);//调用被隐藏的继承函数
return 0;
}
三、动态链接-虚函数表
相关概念:
联编 | 将模块或函数合并在一起生成可执行代码 |
静态联编 | 就是静态链接,编译阶段,也叫早绑定 |
动态联编 | 就是动态链接,执行阶段,也叫晚绑定。 涉及到多态和虚拟函数就必须要使用动态联编 |
虚函数表 | 使程序达成动态联编。 |
代码1:区分静态链接与动态链接与隐式转换
因为Inherit是Base的子类,可以使用Base类型的变量接收Inherit类型的变量,系统会自动隐式转换
void test(Base &obj){
obj.prnmsg();
}
//如果prnmsg是虚函数,则除了Base &类的对象,其子类在调用该函数时,都使用动态链接(调用相应的prnmsg()) --- 隐式转换
//如果prnmsg不是虚函数,则父类及其子类都调用Base的prnmsg(); --隐式转换
代码2:虚函数表
在C++中,对象的虚函数表通常是一个指向虚函数的指针数组。每个类都有一个虚函数表,而每个对象都有一个指向其所属类的虚函数表的指针。这个指针通常被存储在对象的内存布局中,而不是对象本身。
通过将对象的地址转换为长整数指针并解引用它,我们可以获得存储在对象中的虚函数表指针的值,即虚函数表的首地址。这个地址通常存储在对象的内存布局中的某个固定偏移量上。
目前我是这么理解的:先把obj对象的地址&obj转化为长整型数// long vaddr = (long *)&obj
两边同时取地址可得,&vaddr = (ling *)&obj
PFUNC是指针类型,好比char *
函数指针的指针名+() 表示调用该函数
..........
函数指针:void (*p)(void) ,p是指针类型,可以取别名为PFUNC,指针类型比如int * char *...
eg:
void (*p)(void) = myFunction; //表示一个指针,是一个整体
p(); // 调用 myFunction //特殊的调用函数方式
#include <iostream>
using namespace std;
class Base{
public:
virtual void funa(void)
{
cout << __func__ << ":" << __LINE__ << endl;
}
virtual void funb(void)
{
cout << __func__ << ":" << __LINE__ << endl;
}
};
class Inherit : public Base{
public:
void funa(void)
{
cout << __func__ << ":" << __LINE__ << endl;
}
void funb(void)
{
cout << __func__ << ":" << __LINE__ << endl;
}
};
//---------------------------------------------------------------//
typedef void (*PFUNC) (void);//函数指针
int main()
{
Base obj;
long vaddr = *(long *)&obj;//得到虚函数表的首地址
PFUNC fun = (PFUNC)*(long *)vaddr;//得到虚函数表中登记的第一个虚函数的首地址
fun();//调用funa()
fun = (PFUNC)*((long *)vaddr + 1);//
fun();
return 0;
}
四、抽象类
父类有纯虚函数的类叫抽象类,在父类public中声明=0,在子类public中实现
派生类应该实现抽象类的所有方法
应用:比如一个点派生出圆形,矩形,三角形. 那么求面积函数就可以定义为纯虚函数wu
#include <iostream>
using namespace std;
class Graphic{
public:
virtual double area(void) = 0;//纯虚函数 在派生类中实现
};
class Circle : public Graphic{
public:
Circle(double r) : r(r)
{
}
public:
double area(void) //重写基类的纯虚接口
{
return r*r*3.14;
}
private:
double r;
};
int main()
{
Graphic obja;//error 抽象类不能有对象
Circle objb(5);
cout << objb.area() << endl;
return 0;
}
五、虚继承(多重继承 主函数使用时加 类名::)
解决多重继承产生的二义性
class Base{
public:
Base()
{
cout << __func__ << ":" << __LINE__ << endl;
}
~Base()
{
cout << __func__ << ":" << __LINE__ << endl;
}
public:
void prnmsg()
{
cout << __func__ << ":" << __LINE__ << endl;
}
};
class Man : public virtual Base{//虚继承:解决多重继承产生的二义性
public:
Man()
{
cout << __func__ << ":" << __LINE__ << endl;
}
~Man()
{
cout << __func__ << ":" << __LINE__ << endl;
}
};
class Wolf :public virtual Base{
public:
Wolf()
{
cout << __func__ << ":" << __LINE__ << endl;
}
~Wolf()
{
cout << __func__ << ":" << __LINE__ << endl;
}
};
//多重继承
class WolfMan : public Wolf,public Man{
public:
WolfMan()
{
cout << __func__ << ":" << __LINE__ << endl;
}
~WolfMan()
{
cout << __func__ << ":" << __LINE__ << endl;
}
};
int main()
{
WolfMan obj;
obj.prnmsg();//多重继承产生二义性
return 0;
}
六、虚析构函数(最好都定义为虚析构,防止空间回收不完整)
父类虚析构就行了,子类自动虚析构
父类指针指向子类空间的时候,防止回收不完整
class Base{
public:
Base(int val=0) : val(val)
{
cout << __func__ << ":" << __LINE__ << endl;
}
virtual ~Base()
{
cout << __func__ << ":" << __LINE__ << endl;
}
private:
int val;
};
class Inherit : public Base{
public:
Inherit(int val = 0) : myval(val)
{
cout << __func__ << ":" << __LINE__ << endl;
}
~Inherit()//基类析构为虚时,子类析构自动为虚
{
cout << __func__ << ":" << __LINE__ << endl;
}
private:
int myval;
};
int main()
{
Base *p = new Inherit;//类型转换 Inherit * - > Base *
//用空间来理解:子类中包含了父类的val,所以如果不加虚析构就写Base *p就只会释放val的那一部分
delete p;//子类对象析构时,基类也会被析构
return 0;
}
Base *p = new Inherit;//类型转换
怎么理解这行代码:子类空间中会包含父类的成员(可用sizeof验证),用空间来理解,如果隐式转换为Base类型,使用普通析构函数时,只会释放Base,如果使用虚析构函数,则Base和Inherit都会析构一次(因为本质还是Inherit创建的对象,而子类创建对象又必须途径父类)
七、限制构造函数(加友元)
构造函数的权限不是public,这样的构造函数叫限制构造函数。
需要友元函数
class Base{
protected:
Base() //限制构造函数
{
cout << __func__ << ":" << __LINE__ <<endl;
}
public:
~Base()
{
cout << __func__ << ":" << __LINE__ <<endl;
}
};
class Inherit : public Base{
public:
Inherit()
{
cout << __func__ << ":" << __LINE__ <<endl;
}
~Inherit()
{
cout << __func__ << ":" << __LINE__ <<endl;
}
};
int main()
{
Base obj;//error
Inherit obj;//right
return 0;
}
eg2:
class Demo{
private:
Demo() //限制构造函数
{
cout << __func__ << ":" << __LINE__ <<endl;
}
public:
~Demo()
{
cout << __func__ << ":" << __LINE__ <<endl;
}
friend Demo *getobj(void);
friend void freeobj(Demo *p);
};
Demo *getobj(void)
{
Demo *p = new Demo;
return p;
}
void freeobj(Demo *p)
{
delete p;
}
int main()
{
Demo *p = getobj();
freeobj(p);
return 0;
}