虚函数和多态

知识储备:

重提切片行为:

1.子类对象赋值给父类对象

2.父类型的指针指向了子类对象

3.父类型的引用引用子类的对象

虚函数:类的成员函数前面加virtual关键字,则这个成员函数称为虚函数

虚函数重写:当在子类的定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也称覆盖)了父类的这个虚函数

构成多态需要满足的条件:

1.函数的形参是父类的指针或者引用

2.调用的Func必须是虚函数的重写

构成多态的函数的调用只与调用函数函数的对象有关,(父类对象还是子类对象)

常规的函数调用只与类型有关,(父类还是子类)

小记:

1>虚函数使用基类指针或者引用调用时会发生动态绑定,

2>动态绑定又称晚绑定(运行时绑定),函数运行时的版本由实参决定,即在运行时选择函数版本。

3>类成员函数没有声明为虚函数,则其解析过程发生在编译而非运行时。

class Person
{
public:
	virtual void BuyTickets()
	{
		cout << " 全价" << endl;
	}
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	virtual void BuyTickets()
	{
		cout << "半价" << endl;
	}
protected:
	int _num; //学号
};
//void Fun(Peron p)            //不构成多态,与类型有关,这里都调用的是基类的函数
//void Fun(Person* p)          //构成多态
void Fun(Person& p)		//构成多态
	// 不够多态的常规调用 -- 跟类型有关
	// 构成多态 -- 跟对象有关
	//构成多态的行为
	// 1.父类指针和引用
	// 2.调用的func必须是虚函数的重写
{
	p.BuyTickets();
}
void Test()
{
	Person p;
	Student s;
	Fun(p);
	Fun(s);
}

总结:

1. 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)

2. 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性

3. 只有类的非静态成员函数才能定义为虚函数, 静态成员函数不能定义为虚函数

4. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual。

5. 构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但是最好不要将operator=定义为虚函数,因为容易使用时容易引起混淆。

6. 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。

7. 最好把基类的析构函数声明为虚函数。(why?析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里是因为编译器做了特殊处理)

8.虚表是所有类对象实例共用的

class A                              //把基类的析构函数最好声明为虚函数
{
public :
	virtual ~A()
	{
		cout << "~A()" << endl;
	}
};

class B :public A
{
public:
	~B()
	{
		cout <<"~B()" << endl;
	}
};

int main()
{
	B b;
	A& ra = b;
	A* p = new B;		
	delete p; // p->destructor() + free(p)----只析构了A没析构B,如果~B()中有释放内存的操作,就会造成内存泄漏
                 //解决:将父类的析构函数定义成虚函数
                //这里先发生切片,A类型的指针p 指向了B对象,
	       //当将父类的析构写成虚函数,在实际调用的时候,构成多态与类型指针的类型无关,直接调B的析构
	system("pause");
	return 0;
}

纯虚函数:在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

class Person
{
public:
	virtual void Display() = 0;                      //纯虚函数
protected:
	string _name;
};

class Teacher : public Person
{
	virtual void Display()
	{
		cout << "Teacher" << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
	virtual void Display()
	{
		cout << "Student" << endl;
	}
protected:
	string _name;
};

int main()
{
	//Person p;                 //出错-抽象类无法创建对象
	Person* p = new Teacher;
	p->Display();

	p = new Student;
	p->Display();

	return 0;
}

解惑:

1.什么是协变?

    函数的返回值类型可以不同,但返回值类型必须是本类的指针或者引用。

2.构造函数为什么不能声明称虚函数?

   1>构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。

    2>虚函数的执行依赖于虚函数表。而虚函数表在构造函数的初始化列表中进行初始化工作,即初始化vptr,让它指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。

多态原理:

将父类的函数声明成虚函数,子类继承时就会生成自己虚表,即父子类具有各自不同的虚表。

当指针(或引用)指向父类时,就在父类的虚表中查找指定的函数,当指针执行子类时,便在子类的虚表中查找。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值