C++多态实现

构造函数

类的实例化,必须通过构造函数。

通过类名实例化对象,会默认调用构造函数和析构函数;

Base a;    // 自动调用构造函数去实例化对象
Base b;	   // 程序结束也会自动调用析构函数,先进后出

调用顺序

通过类指针和new去实例化对象,仅定义指针则不调用构造函数、仅new则仅调用构造函数、只有手动new和delete才会调用构造函数和析构函数(不排顺序,即写即操作)。
先基类,后派生类。

Base *a;				// 不调用任何函数
Base *b = new Base();	// 调用构造函数
Base *c = new Base();	// 调用构造函数
delete c;				// 调用析构函数(马上)




虚函数

基于虚函数表的底层实现

​不能写成虚函数:

  • 友元函数,它不是类的成员函数
  • 全局函数
  • 静态成员函数,它没有this指针
  • 构造函数,拷贝构造函数,以及赋值运算符重载(可以但是一般不建议作为虚函数)

虚函数表

在函数的定义前加virtual,则该类的内存会多一个指向虚函数表的指针(4位或8位)。实例化对象过程中,如果对象存在虚函数,那么编译器就会生成一个指向虚函数表的指针,所有的虚函数都存在于这个表中,虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。

虚函数表一般在对象的最前面

重写虚函数后会将重写内容覆盖原虚函数表,否则直接继承基类的表,若既有重写又重新定义了新的虚函数,则先覆盖,再将新的虚函数接在继承来的表后面,具体如下:

class Base {
public:
	virtual void a() {cout << "Base a()" << endl; }
    virtual void b() {cout << "Base b()" << endl; }
    virtual void c() {cout << "Base c()" << endl; }
};
class Derive : public Base {
public:
    virtual void b() {cout << "Derive b()" << endl; }
};

int main() {
	// 基类指针指向派生类
	Base *p = new Derive();
	p->a();
	p->b();
	p->c();
	delete p;
}

在这里插入图片描述
如果是多继承,则继承类有多个虚函数表,重写的虚函数覆盖在继承的表上,新定义的虚函数则写在第一个表的后面,具体如下:

class Base1 {
public:
	virtual void a() {cout << "Base1 a()" << endl; }
    virtual void b() {cout << "Base1 b()" << endl; }
    virtual void c() {cout << "Base1 c()" << endl; }
};
class Base2 {
public:
	virtual void d() {cout << "Base2 d()" << endl; }
    virtual void e() {cout << "Base2 e()" << endl; }
};
class Derive : public Base1, public Base2 {
public:
    virtual void a() {cout << "Derive a()" << endl; }
    virtual void d() {cout << "Derive d()" << endl; }
    virtual void f() {cout << "Derive f()" << endl; }
};

在这里插入图片描述


创建时机

对于虚函数表来说,在编译的过程中编译器就为含有虚函数的类创建了虚函数表,并且编译器会在构造函数中插入一段代码,这段代码用来给虚函数指针赋值。因此虚函数表是在编译的过程中创建

对于虚函数指针来说,由于虚函数指针是基于对象的,所以对象在实例化的时候,虚函数指针就会创建,所以是在运行时创建。由于在实例化对象的时候会调用到构造函数,所以就会执行虚函数指针的赋值代码,从而将虚函数表的地址赋值给虚函数指针。

// 静态联编:在编译时所进行的这种联编又称静态束定,在编译时就解决了程序中的操作调用与执行该操作代码间的关系。 
// 动态联编:编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序运行时才能确定将要调用的函数,为此要确切知道该调用的函数,
// 要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编。

class A{
public:
	int x;
	A(){
		memset(this, 0, sizeof(x));       // 将this对象中的成员初始化为0
		cout << "构造函数" << endl;
	}
	A(const A& a) {
		memcpy(this, &a, sizeof(A));      // 直接拷贝内存中的内容
		cout << "拷贝构造函数" << endl;
	}
	virtual void virfunc() {
		cout << "虚函数func" << endl;
	}
	void func() {
		cout << "func函数" << endl;
	}
	virtual ~A() {
		cout << "析构函数" << endl;
	}
};

A a;			// 静态编连
a.virfunc();    // 可以调用该虚函数,说明是静态编连,构造函数里将虚函数指针置空,没有实质改变该虚函数的指向

A *a = new A(); //  动态编连
a->virfunc();   //  无法编译通过,说明是动态编连,虚函数指针置空后无法再调用到该虚函数

/*  所以说,实现多态的时候尽量还是用 指针或者引用 去实例化对象(记得要手动delete和置空该指针)  */




纯虚函数

class Base {
public:
    virtual void func() = 0;  // 纯虚函数
};

在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。纯虚函数是一定要被继承的,否则它存在没有任何意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值