清华主页 - 清华新闻 - 综合时讯 - 正文

多态化的使用和原理(c 详解)

一、多态概念。

        多态顾名思义是多种形式,它分为。多态(编译时;静态多态)xff0;和。多态运行(动态多态),编译时多态(静态多态)xff0;就是。函数重载󿀌模板。等,不同函数的调用(通过不同的参数完成;生成多种形式)而这个过程。已完成编译阶段。。

        动态多态是在。运行时。根据对象的实际类型确定哪个版本的调用函数,完成不同的⾏为。

二、多态构成条件。

1.虚函数。

        添加类成员函数的返回类型。virtual关键词。即虚函数,注意:虚拟函数只能定义为普通成员函数,虚函数不能定义为构造函数和类外函数。

2.重写虚拟函数。

        虚拟函数重写是指子类(#xfff09;对父亲�基类)的重写。重写的要求是子类虚拟函数的返回值,函数名,参数类型必须与父类完全相同。但是函数的实现逻辑不一样。

        如果虚拟函数的重写不添加virtual,但是,如果父类添加virtual,子类仍然保持virtual的性质,也可构成重写。

        。注意:虚拟函数重写不需要相同的缺失参数,但这里强烈建议将缺失参数设置为相同值,否则会给你带来很大的弊端和误导性。接下来,我将讨论它。

3.调用方法。

        实现多态效果,第⼀必须是基类。指针或引⽤。,因为只有基类指针或引用⽤才能。既指向派⽣类对象也可以指向基类。;第⼆派⽣类必须重写/覆盖基类的虚拟函数,重写或覆盖,派⽣类可以有不同的函数,只有达到多态不同形态的效果。

⽐例如,火车购票操作,当普通⼈࿰买票c;全价买票;学⽣࿰买票c;优惠买票;军⼈买票时优先买票,我们可以使用多态来实现,以下:

#includeusing namespace std;class ticket{ public:	virtual void func()	{ 		cout << "普通票" << endl;	}private:};class student:public ticket{ public:	virtual void func()	{ 		cout << "学生票" << endl;	}private:};void fm(ticket& pu){ 	pu.func();}int main(){ 	ticket tk;	student stu;	fm(tk);	fm(stu);	return 0;}。

4.装饰override和final。

        override关键字:因为实现多态的细节要求太多,尤其是虚拟函数的重写,因此C++11提供override,可以帮助⽤⼾检查虚拟函数的重写是否正确,需要放在重写函数参数列表后面。

        final关键字:如果子类不想重写虚拟函数,可以使用final关键字,放在函数名后面。

        final关键字:如果子类不想重写虚拟函数,可以使用final关键字,放在函数名后面。

5.协变 。

        刚才我们说虚拟函数的重写必须满足子类虚拟函数的返回值,函数名,参数类型必须与父类相同。协变是个例外。当子类重写父类虚拟函数时,#xff0c;如果父类虚函数的返回值类型不同,即父类虚拟函数返回父类对象的指针或引用,子类虚函数返回子类对象的指针或引用,此时称为协变。协变的现实意义并非如此⼤,所以我们知道⼀下即可。

#xff1代码示例a;

class A{ };class B :public A{ };class ticket{ public: virtual ticket* func()//ticket也可以是A { cout << "普通票" << endl; return this; }private:};class student:public ticket{ public: virtual student* func() override//student也可以是B { cout << "学生票" << endl; return this; }private:};

重写分析函数。        父类的分析函数是虚拟函数,此时派⽣只要定义了类析构函数,⽆是否添加virtual关键字,都与基类分析函数构成重写,尽管基类和派⽣不同的类析构函数名称似乎不符合重写规则,事实上,编译器对分析函数的名称进行了特殊处理,编译后析构函数的名称统一⼀处理成destructor。因此,vialtual修饰࿰添加了基类的分析函数c;派⽣类的析构函数构成重写。

因此,vialtual修饰࿰添加了基类的分析函数c;派⽣分析函数构成重写。

A* p1 = new A;A* p2 = new B;delete p1;delete p2;        假设B是A的子类⾯如果代码~A(),不加virtual,所以delete p2时只调⽤A的分析函数,没有调⽤B分析函数,会导致内存泄漏。三、纯虚函数和抽象类。三、纯虚函数和抽象类。        在虚拟函数的参数列表后⾯写上=0,这个函数是纯虚函数,纯虚函数不需要定义和实现(实现没有意义,因为被子类重写󿀌但是语法上可以实现,只需声明即可。包含纯虚函数的类称为抽象类,

抽象类不能实例化对象。

子类继承后不重写纯虚函数,所以子类也是抽象的。。在某种程度上,纯虚函数强制子类重写虚函数,因为不重写实例化的对象。四、多态原理。        在分析对象的存储空间时,我们谈到了同一类实例化的不同对象,这些对象使用相同的函数,不同之处在于它们的成员变量。因此,每个对象只需要存储成员变量,不需要存储成员函数#xff0c;每个对象都使用这类公共成员函数。       。

c++为虚拟函数设置一个单独的区域来存储虚拟函数的地址,叫做虚表。

,事实上,这个区域 是函数指针数组。也就是一个用来存储函数指针的数组。那么父子之间就有了自己的虚表,当对象实例化时,它将隐含(隐含:类似于成员函数中看不见的this指针)指针-。

虚表指针。

,指向虚表。

#includeusing namespace std;class A{ public: virtual void func(){ }};class B{ public: void func(){ }};int main(){ A a; B b; cout << "a:" << sizeof(a) << endl; cout << "b:" << sizeof(b) << endl; return 0;}。

虚表指针也需要占用空间。您可以自行操作上述代码,输出结果为:

a:4(或8󿀌也就是说,32位和64位机器的区别)。

虚表指针也需要占用空间。您可以自行操作上述代码,输出结果为:a:4(或8￰)c;也就是说,32位和64位机器的区别)。

b:1。

        因此,在调用对象的虚拟函数时,与以何种形式调用无关,但与实例化对象的具体类型有关。

  • #includeusing namespace std;class ticket{ public: virtual void func() { cout << "普通票" << endl; }};class student :public ticket{ public: virtual void func() { cout << "学生票" << endl; }};int main(){ ticket* tk = new student; tk->func(); return 0;}。
  • 以上输出结果为“学生票”。
  • 以上输出结果为“学生票”。
  • 注意:
  • 根据切片原理󿀌子类可以强制类型转化为父类,父亲不能强迫类型转化为子类。
  • 五、练习。

以下程序输出结果是什么()

A:A->0。

B:B->1。

C:A->1。D:B->0。E:编译出错。F:以上都不正确。        虽然B类func成员没有写virtual关键词,但它是由A继承的,仍然保留着virtual的性质,然后,因为重写和所需参数的缺失值相同,因此,这里构成函数的重写。再来看看主函数main,test࿰调用test&c;test是a的成员函数,它隐含了一个constt   A* (this指针)参数类型,test函数满足多态,所以这里调用的是B的func。但是这里有一个坑󿀌这个问题的输出结果不是“B->0",而是“B->1”。        注意重写只是重写函数的实现,也就是说,实现多态相当于调用父类接口声明和子类函数实现,并且不关心子类的函数接口声明。        所以当我们自己写虚拟函数时,最好将缺失参数设置为相同的值󿼌否则会给你带来很大的误导。

2025-06-24 11:44:52

相关新闻

清华大学新闻中心版权所有,清华大学新闻网编辑部维护,电子信箱: news@tsinghua.edu.cn
Copyright 2001-2020 news.tsinghua.edu.cn. All rights reserved.