类与多态-多态的概念

多态是函数调用的多种形态,分为静态多态(函数重载,编译期间确定)和动态多态(虚函数,运行时确定)。动态多态依赖于虚函数表来找到正确的方法实现。实现多态需要满足两个条件:调用的函数必须是虚函数,且使用父类指针或引用。抽象类通过包含纯虚函数定义接口,子类必须重写纯虚函数才能实例化。析构函数使用虚析构防止内存泄漏问题。
摘要由CSDN通过智能技术生成

多态的概念:

多态就是函数调用的多种形态,使用多态能够使得不同的对象去完成同一件事时,产生不同的动作和结果。

多态又分为静态多态和动态多态:
(1)静态多态,也称为静态绑定或前期绑定(早绑定):函数重载和函数模板实例化出多个函数(本质也是函数重载)。静态多态也称为编译期间的多态,编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。

(2)动态多态,也称为动态绑定或后期绑定(晚绑定):在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,即运行时的多态。在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。

父类指针或引用指向父类,调用的就是父类的虚函数
父类指针或引用指向子类,调用的就是子类的虚函数

为什么动态多态无法在编译时就确定

在编译的时候编译器并不知道用户选择的是哪种类型的对象。如果不是虚函数,则采用静态绑定,函数体与函数调用在程序运行之前(编译期间)就绑定了。
当函数声明为虚函数时,如果使用指针或引用的形式,那么由于指针指向的对象不确定是基类还是派生类,那么编译器就无法采用静态绑定,所以就只有通过动态绑定的方式。因此编译器通过创建一个虚函数表存放虚函数的地址,在运行时,编译器通过虚函数指针在虚函数表中找到正确的函数版本,然后调用。

多态的两个要求:

 1、被调用的函数必须是虚函数(注意,只能调用虚函数)子类对父类的虚函数进行重写 (重写:三同(函数名/参数/返回值)+虚函数)
 2、父类指针或者引用去调用虚函数。

#include<iostream>
using namespace std;

//基类
class Print1
{
public:
    //无参构造
    Print1(){}
    //有参构造
    Print1(string name1) {
        this->Name1 = name1;
    }
    //返回Name1
   virtual string getName() {
        return this->Name1;
    }
    //输出Print1
    virtual void show() {
        cout << "Print1的打印" << endl;
    }

private:
    string Name1;
};

//子类
class Print2:public Print1
{
public:
    //无参构造
    Print2() {};
    //有参构造
    Print2(string name2) {
        this->Name2 = name2;
    }
    // 重写父类getName() 返回Name2
     string getName() {
        return this->Name2;
    }
     // 重写父类show() 输出Print2
    void show() {
        cout << "Print2的打印" << endl;
    }

   
   

private:
    string Name2;
};

//通过父类的引用调用虚函数
void PrintName(Print1 &P)
{
    P.show();
}


int main()
{             //Name1             //Name2
    Print1 P1("Name1"); Print2 P2("Name2");
   
    Print1* p1 = &P1;
    Print2* p2 = &P2;

    
    //通过父类指针调用虚函数
    cout << p1->getName() << endl; //  P1.getName()被调用,返回Name1
    
    p1 = p2;
    cout << p1->getName() << endl;  //P1.getName()被调用,返回Name1


    //通过父类的引用调用虚函数
    PrintName(P1);
    PrintName(P2);


    return 0;
}

根据C++11特性,子类重写父类虚函数的时候,我们可以在子类重写函数后面加入override,以防bug发生

override关键字作用:

如果派生类在虚函数声明时使用了override描述符,
那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。

简单来说就是检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

 注释掉父类函数,子类getName()之后加上override

此时会发生

抽象类:

虚函数后面加上=0就是纯虚函数,包含纯虚函数的类即为抽象类(接口类)抽象类不能实例化出对象派生类继承抽象类后若没有重写纯虚函数那么仍为抽象类,亦不能实例化出对象。纯虚函数规范了派生类必须重写虚函数,并且更加体现出了接口继承

子类没有重写抽象类

#include<iostream>
using namespace std;

//纯虚函数
class Base
{
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类称为抽象类
	//抽象类特点:
	//1.无法实例化对象
	//2.抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类

	virtual void func() = 0;
};

class Son :public Base
{
public:
	
};

void test01()
{
	//Base b; //抽象类是无法实例化对象
	//new Base; //抽象类是无法实例化对象
	//Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象

}



int main()
{
	test01();
	

	
	return 0;
}

子类重写抽象类

#include<iostream>
using namespace std;

//纯虚函数
class Base
{
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类称为抽象类
	//抽象类特点:
	//1.无法实例化对象
	//2.抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类

	virtual void func() = 0;
};

class Son :public Base
{
public:
	void func()  //子类重写抽象类
	{
		cout << "func函数调用" << endl;
	}
};

void test01()
{
	//Base b; //抽象类是无法实例化对象
	//new Base; //抽象类是无法实例化对象
	Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象
	Base* base = new Son;
	base->func();

}



int main()
{
	test01();
	

	
	return 0;
}

析构函数的重写:虚析构和纯虚函数。

使用析构函数的重写,使得发生多态时,子类的析构函数也能调用

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A的构造" << endl;
	}

	~A()
	{
		cout << "A的析构" << endl;
	}
	//纯虚函数
	virtual void show()=0;
	

};

class B:public A
{
public:
	B(int num)
	{  
		//将这个成员属性指向堆区
		this->Num = new int(num);
		cout << "B的构造" << endl;
	}

	//堆区数据手动开辟,手动释放
	~B()
	{
		if (Num != NULL) {
			delete Num;
			Num = NULL;
		}

		cout << "B的析构" << endl;
	}
	void show()
	{
		cout << "num=" << *Num << endl;
	}

	//创建指针类型的成员属性
	int* Num;
};


int main()
{
	A* a = new B(10);
	a->show();
	//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
	delete a;

	return 0;
}

可以看到,父类指针或者引用指向派生类对象,父类指针在析构时候,只调用了父类析构,导致子类对象中有堆区属性,出现内存泄漏。

解决,父类析构使用虚析构函数或者纯析构函数。

虚析构

纯虚析构

这里注意,在类内声明纯虚析构函数时,类内声明,类外要初始化

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A的构造" << endl;
	}

	//虚析构函数
	/*virtual~A()
	{
		cout << "A的析构" << endl;
	}*/

	//纯虚析构
	virtual ~A() = 0;

	//纯虚函数
	virtual void show()=0;
	

};
//纯虚析构需要类内声明,类外初始化
A::~A() { cout << "A的析构" << endl; }


class B:public A
{
public:
	B(int num)
	{  
		//将这个成员属性指向堆区
		this->Num = new int(num);
		cout << "B的构造" << endl;
	}

	//堆区数据手动开辟,手动释放
	~B()
	{
		if (Num != NULL) {
			delete Num;
			Num = NULL;
		}

		cout << "B的析构" << endl;
	}
	void show()
	{
		cout << "num=" << *Num << endl;
	}

	//创建指针类型的成员属性
	int* Num;
};


int main()
{
	A* a = new B(10);
	a->show();
	//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
	delete a;

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值