Day 9 虚函数和多态

一、虚函数和虚函数表

1.虚函数:用virtual修饰的成员函数叫做虚函数。

2.虚函数对类的影响:

①无论有多少个虚函数,增加类空间的一个指针的字节(是原先的1个字节不需要了换成了4个字节)

注:①c++中空的类or结构体占用一个字节

②普通函数是不会影响类所占有的内存的

②类内声明,类外限定须类名限定,但无需virtual修饰。

3.虚函数表(了解一下):就是一个指针存储所有虚函数的首地址

一般对象(当成一个二维数组)的首地址 存的是一排虚函数的首地址,于是我们便可以通过虚函数表来调用这些虚函数。

	A a;
	//虚函数表 
	int** vptr = (int**)&a;
	typedef void(*PF)();
	PF func = (PF)vptr[0][0];
	func();		//调用第一个虚函数
	func = (PF)vptr[0][1];
	func();		//调用第二个虚函数

了解即可,不做深究。

二、虚函数和多态 

1.多态必为公有继承,同一个调用(方法)导致不同的结果。

2.多态必要性原则:

        ①父类必须有虚函数

        ②子类必须要用public继承(有无虚函数无所谓)

        ③必须存在指针的引用。(使用)

           ···1、正常访问、指针访问(正常赋值)(同名函数)均为“就近原则”。

           ···2、指针非正常赋值:子类对象初始化父类指针。

                        当父类函数有virtual =》看对象类型。(可以实现父调子类中的同名函数)

                        当父类函数无virtual=》看指针类型。(父调父)

以上体现了,相同的行为产生了不同的结果。

于是,我们可以“统一接口”:(降低因为变化而要修改代码的情况,只需要重写父类的方法,采用增加代码的方式满足新需求)通过new一个子类对象,来初始化父类指针形参

例,画图

#include<iostream>

using namespace std;

class shape
{	
public:
	virtual void Draw()
	{
		cout << "绘画过程" << endl;
	}
protected:
};

class Rec :public shape
{
public:
	void Draw()
	{
		cout << "画矩形" << endl;
	}
protected:
};
class Triangle :public shape
{
public:
	void Draw()
	{
		cout << "画三角形" << endl;
	}
};
class Tool
{
public:
	void draw(shape* a)
	{
		a->Draw();
	}
};
int main()
{
	Tool tool;
	tool.draw(new Rec);
	tool.draw(new Triangle);
}

三、纯虚数和ADT 

1.纯虚数是没有函数体虚函数

        类中的写法:virtual void print()=0;

2.抽象类:具有至少一个纯虚函数的类,叫做抽象类。

        ①抽象函数不能构建对象

        ②抽象函数可以构建对象指针。

3.纯虚函数的作用:做ADT(abstract data type抽象数据类型)过程(可以一个人搭建代码框架,分发他人去写代码)

4.纯虚函数没有被重写的话,无论继承多少次都为纯虚函数,虚函数也是。

5.做ADT:①父类中的所有操作方法描述好

                 ②子类想要创建对象,必须重写父类的纯虚函数。

                ③ADT具有强迫性,所有子类重写函数必须和父类一模一样

                        注:子类是可以增加别的函数,别的数据成员。

例:栈:

#include <iostream>
using namespace std;

//stack  栈
class stack
{
public:
	//父类中所有的操作描述好
	virtual void push(int data) = 0;
	virtual void pop() = 0;
	virtual int top() const = 0;
	virtual bool empty() const = 0;
	virtual int size() const = 0;
};
class arrayStack :public stack
{
public:
	void push(int data){}
	void pop(){}
	int top() const
	{return  1;}
	bool empty() const
	{return true;}
	int size() const
	{return 1;}
	//可以增加别的函数
	//可以增加别的成员
protected:
	int* array;
};
struct Node
{
	int data;
	Node* next;
};
class listStack :public stack
{
public:
	void push(int data){}
	void pop(){}
	int top() const
	{return  1;}
	bool empty() const
	{return true;}
	int size() const
	{return 1;}
protected:
	Node* headNode;
};
void testStack(stack* pStack)
{
	pStack->push(1);
	while (!pStack->empty())
	{
		cout << pStack->top();
		pStack->pop();
	}
}
int main()
{
	testStack(new arrayStack);
	testStack(new listStack);

	return 0;
}

 补充:与多态结合。纯虚函数被子类重写后属性仍然为virtual

#include<iostream>

using namespace std;
class A
{
public:
	virtual void print() = 0;
protected:
};
class B :public A
{
public:
	void print()
	{
		cout << "B" << endl;
	}
};
class C :public B
{
public:
	void print()
	{
		cout << "C" << endl;
	}
};

void Abtract()
{
	//B b;
	C c;  //一般抽象类只被继承一次就重写
	B* pc = new C;
	pc->print();
}
int main()
{
	Abtract();
	return 0;
}

B是有virtual 的,所以调用的print看对象类型也就是C,输出结果为 C

四、虚析构函数:

Q:非正常赋值中,parent *p =new  son;....................  delete p;

发现并不会调用子类的虚构函数=>内存泄漏

#include<iostream>

using namespace std;

class parent
{
public:
	~parent()
	{
		cout << "父类的析构函数" << endl;
	}
	void print()
	{}
protected:

};

class son:public parent
{
public:
	~son()
	{
		cout << "子类的析构函数" << endl;
	}
protected:
};

int main()
{
	parent* a = new son;
	delete a;//手动delete别忘了
	return 0;
}

 

解决:父类的析构函数前加上一个virtual

class parent
{
public:
	virtual ~parent()
	{
		cout << "父类的析构函数" << endl;
	}
	void print()
	{}
protected:
};

 

注:不存在虚构造函数。

五、final和override两个关键字 

1、final:禁止重写(限制子类)子类不可存在同名函数

(只能用父类对象调用,只用不改)

virtual void print() final
{
    cout<<"重写在虚函数中才有,记得加virtual"<<endl;
}

 2、override:强制重写,标识作用,用检查父类中是否存在当前的虚函数。

C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错

说白了就是基类的虚函数指针没有被派生类覆盖(目的是希望派生类重写对应的虚函数,但由于不小心写错了参数),导致通过基类指针调用了基类的虚函数,而不是派生类对应的函数

virtual void fun1() const override;  // adding "virtual" is OK, but not necessary
    virtual void fun2(int x) override;

 六、C++类型转换:(专人做专事)

const_cast<要转换成的类型>(要转换的目标)

1、去掉const属性(提供一个可以修改的接口去操作const属性变量)

2.也可以加上const属性(少)

实例:

        ①操作类中的char* 指针

class A
{
public:
	A(const char *name):name(const_cast<char*>(name)){}
protected:
	char* name;
};//避免了传一个字符串变量失败

此后在传字符串常量or变量的时候均不会报错。

        ②作用于普通指针:

	const int num = 11;
	int* pNum = const_cast<int*>(&num);
	*pNum = 1123;
	cout << num << endl;

最终结果仍然是num为11. (能用不能改)

         ③操作 类指针:

class B
{
public:
	B(int num=1):num(num){}
	void print()
	{
		cout << num << endl;
	}
protected:
	int num;
};
int main()
{
	const B* pB = new B(111);
	B* ppB = const_cast<B*>(pB);
	ppB->print();
	return 0;
}

        ④操作常量引用也是可以的。

②、static_cast <要转换成的类型>(要转换的目标)

        ①基本数据类型转换(类似于C的强制类型转换)

        ②把空指针类型转成目标类型指针

        ③把任何类型的变量转换为void指针

        ④用在类上面的转换(基类和派生类对象之间的转换)

                4.1进行上行转换(从子->父  指针or引用转换  安全)

                4.2进行下行转换(从父->子   。。。。。。。)不安全,需要是在两者均为对应类的指针的情况下才能进行转换

        注:static_cast不能转换const

reinterpreter_cast<要转换成的类型>(要转换的目标) 数字和指针来回互换

例:

#include<iostream>
using namespace std;

int Max(int a, int b)
{
	return a > b ? a : b;
}
int main()
{
	int num = reinterpret_cast<int>(Max);//Max是一个函数指针(地址)
	cout << num << endl;
	cout << Max << endl;
	auto pMax = reinterpret_cast<int(*)(int, int)>(num);
	cout << pMax(1, 2) << endl;
	return 0;
}

④、dynamic_cast <要转换成的类型>(要转换的目标)

        1、上行转换:和static_cast一样

        2、下行转换:dynamic_cast更为安全(无virtual会报错、调用子类中父类函数没有的函数会报错,static测不出)->可以用来检查父类是否存在virtual

        3、交叉转换:多继承

                        C继承A和B:①指针A用C来初始化

                                            ②B用A来初始化(c作为一个桥梁)-用dynamic_cast来初始化转化

 

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值