多态
多态的概念
多态即多种形态,当不同的对象去完成某一个行为时产生出不同的状态。类的多态体现在面向对象程序设计的许多方面,函数重载,函数的覆盖继承,虚函数以及模板均是多态性的体现。
多态的种类及不同种类介绍
1>强制多态:强制类型转换
强制多态是指将一种类型的值转换为另一种类型的值进行语义操作,从而防止类型错误。类型转换可以是隐式的,在编译时完成;也可以是显式的,可在动态运行时完成。
1.C++使用两种语法进行强制类型转换:(类型说明符)变量名 / 类型说明符(变量名)
2.构造函数也是一种重要的显示类型转换,构造函数进行的类型转换只能将参数类型向类类型转换。如果要把类类型的数据转换为所指定的某种数据类型,就需要使用类型转换函数。类型转换函数又称为类型强制转换成员函数,他是类中的一个非静态成员函数。需要注意的是类型转换函数不能有返回值,不带任何参数,也不可以将类型转换函数定义为友元函数。
类型转换函数定义格式与实例如下:
class<类型说明符1>
{
public:
operator<类型说明符2>();
…
}
#include<iostream.h>
class Rational
{
public:
operator double();
Rational(int d,int n)
{
den=d;
num=n;
}
private:
int den;
int num;
};
Rational::operator double()
{
return double(den)/double(num);
}
void main()
{
Rational r(5,8);
double d=4.7;
d+=r;
cout<<d<<endl;
}
2>参数类型多态: 函数模板和类模板
参数多态与类模板相关联,它把功能相似仅数据类型(或类类型)不同的函数或类类设计为通用的函数模板或类模板,从而实现了相同的函数或类的成员函数对多种数据类型的数据处理。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础.
1.函数模板
(1)概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
(2)定义
template<typename T1,typenamename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
(3)函数模板的实例化
"1"隐式实例化
让编译器根据实参推演模板参数的实际类型.
"2"显式实例化
在函数名后的<>中指定模板参数的实际类。
template<class T>
T Add(const T& left , const T& right)
{
return left + right;
}
int Test1()
{
int a1 = 10, a2 = 20;
doublr d1 = 10.0, d2 = 20.2;
Add(a1,a2);
Add(d1,d2);
Add(a1,d1);
return 0;
}
int Test2()
{
int a = 10;
double b = 20.0;
Add<int>(a,b);
return 0;
}
Add(a1,d1);语句不能通过编译,因为在编译期间,编译器看到该实例化时会推演其实参类型通过实参a1将T推演为int,通过实参b1将T推演为double类型,但是模板参数列表只有一个T,编译器无法确定此处到底将T确定为int型或者double型而报错。
修改方式:‘1’用户自己来强制转化 Add(a1,(int)b1); ‘2’显式实例化
2.类模板
(1)定义
template<class T1, class T2, ...,classTn>
class Vector
{
};
template<class T>
class Vector
{
pubilc:
......
private:
......
}
template<class T>
vector<T>::~Vector()
{
if(_pData)
{
delete[] _pData;
}
}
Seqlist(vctor)不是具体的类,而是编译器根据被实例化的类型生成具体类的模具。
(2)类模板的实例化
类模板的实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真的类,而实例化的结果才是真正的类。
Vector<int> s1;
s1.PushBack(1);
s1.PushBack(2);
s1.PushBack(3);
3.模板参数
(1)类型参数
template
跟在class或typename之后的类的参数类型。
(2)非类型参数
template<size_t N=10>
用一个常量作为类(函数)模板的参数,该参数可以被当做常量来使用。
3>重载多态(函数重载):函数名相同,但参数类型和参数个数顺序不同构成的多态
重载多态是多态性中最简单的形式,它分为函数重载和运算符重载。
1.函数重载
函数重载允许功能相近的函数使用相同的函数名,编译器根据参数列表的不同区别不同函数。
2.运算符重载
运算符重载是对已存在运算符赋予多重含义,同一个运算符作用于不同的类型数据导致不同类型的行为,它的实质是函数重载。
4>包含多态(运行时多态、虚函数重写实现、虚方法的动态联编):
包含多态是通过虚函数来实现的,同样的操作可用于一个类型及其子类型。包含多态就是运行时多态。如果派生类中覆盖了基类对象的函数,根据赋值兼容,用基类类型的指针或引用指向派生类,就可以通过这个指针或引用来使用派生类的成员函数。如果这个函数是普通成员函数,通过基类指针访问到的只能是基类的成员成员。如果将其设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数,这样通过基类类型的指针就可以使属于不同派生类的不同对象产生不同的行为,从而实现运行时过程的多态。
包含多态的及实现包含多态的构成条件:
1.基类中必须包含有虚函数,在派生类中必须对基类中的虚函数进行重写。
2.必须通过基类的指针或者引用来调用虚函数。
class Person
{
public:
virtual void BuyTicket()
{
cout<<""全价买票""<<endl;
}
};
class Student:public Person
{
public:
virtual void BuyTicket()
{
cout<<""半价买票""<<endl;
}
};
void Func(Person & people)
{
people.BuyTicket();
}
void Test()
{
Person Mike;
Func(Mike);
Student Tom;
Func(Tom);
}
总结:
1.包含多态和参数多态是通用多态,重载多态和强制多态是特定多态。
2.编译时的多态性是指系统在编译时就确定了调用同名函数的哪一个,C++语言中函数重载实现的都是编译时的多态性。
3.运行时多态则需要系统在运行器件根据每一个对象指针指向的对象确定,调用父类或是子类的成员函数,虚函数标识了采用滞后联编实现运行时的多态性。
运行时多态(包含多态详细)
C++语言是利用虚函数实现运行时的多态性的。对于存在虚函数的类层次中,系统将按照滞后联编的方式考虑调用调用对象的成员函数。
1>联编
1.概念及定义
联编是指一个计算机程序自身彼此关联(使一个源程序经过编译、连接,成为一个可执行程序)的过程,在这个联编过程中,需要确定程序中的操作调用(函数调用)与执行该操作(函数)的代码段之间的映射关系。
2.分类
按照联编所进行的阶段不同,可分为静态联编(早期联编)和动态联编(滞后联编)。
(1)早期联编:在对于同名函数的调用时,在编译时决定执行哪个同名函数。
(2)滞后联编:在执行阶段依据要处理的对象的类型来决定执行哪个类的成员函数。
(3)备注:一些面向对象的程序设计语言(Java等)完全使用滞后联编,而C++需要兼容C,所以采用了两种联编方式。C++中虚函数是滞后联编的标识。
3.两种联编的对比
(1)动态联编的功能明显比静态联编强大,但是从效率的执行来看,静态联编在编译连接阶段就完成决策,而动态联编为了使程序能够在运行阶段惊醒决策,必须采取一些方法来跟踪基类指针或引用指向对象类型,这增加了额外的处理开销。基于C++知道原则:不要为不适用的特性付出代价,所以程序默认使用静态联编。
(2)在 C++ 中动态联编需要虚函数的支持,这是因为虚函数的工作原理决定的,而正是因为使用了虚函数来实现动态联编,也让动态联编的效率略低于静态联编。通常,编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员,隐藏成员保存了一个指向函数地址数组的指针 ,这个数组就是虚函数表(virtual function table, vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址,调用虚函数时,程序将查看存储在对象中的 vtbl 地址,然后转向相应的函数地址表,如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。
2>虚函数
1.概念及作用及条件
概念:在类成员函数的前面加virtual关键字就构成了虚函数。
定义:virtual<成员函数原型>
作用:在C++中使用虚函数标志滞后联编。要使用虚函数是,在外部程序中定义一个指向基类对象的指针。根据赋值兼容准则(派生类对象是基类对象,反之不成立),该指针也可指向基类对象,如果该对象指针指向基类对象则调用基类的成员函数,如果该对象指针指向派生类对象则调用派生类的成员函数。
条件:
(1)只有在类层次中才能声明虚函数,虚函数存在于类继承中,只有一个类中存在虚函数是无意义的。
(2)派生类中虚函数的函数名、参数列表和返回值必须与基类中的虚函数完全一样。
(3)派生类必须公有继承基类。
(4)只有成员函数才可定义为虚函数,友元/全局/static函数都不可以。
2.实现
#include<iostream.h>
class A
{
public:
A(void){
}
virtual void f1()const
{
cout<<"基类的f1()函数"<<endl;
}
void f2()const
{
cont<<"基类的f2函数"<<endl;
}
};
class AA:public A
{
public:
AA(void){
}
void f1()const
{
cout<<"派生类的f1()函数"<<endl;
}
void f2()const
{
cont<<"派生类的f2函数"<<endl;
}
};
void main()
{
A *pa,myA;
AA myAA;
pa=&myA;
pa->f1();
pa->f2();
pa=&myAA;
pa->f1();
pa->f2();
myA.f2();
myAA.f2();
}
小结:
(1)派生类中f1()函数前未加virtual关键字仍是虚函数(建议加上),因为在派生类中声明某个成员函数的函数名、参数列表和返回类型和基类中声明的虚函数完全一样,那么系统自动将其视为虚函数。
(2)基类指针根据指针指向的是基类对象还是派生类对象调用对应的虚函数。而覆盖继承在编译时就依据对象指针的类型决定了事中调用某一类对象的成员函数,如f2()函数是一般覆盖继承的函数,那么不管基类指针类pa指向派生类对象或是基类对象,利用pa调用f2()函数始终执行的是基类的f2()函数。
3.关于虚函数的关键字override与final
(1)final修饰虚函数标识虚函数不能被继承。
(2)override修饰虚函数,检查派生类中的虚函数是否重写了基类的虚函数。
4.不能声明为虚函数的有:
友元函数,静态成员函数,类外普通函数,inline函数。
3>虚函数的重写(覆盖)
1.虚函数的重写(函数名,参数列表,返回值相同)
2.虚函数重写的两个例外
(1)协变(基类与派生类的返回值不同),派生类重写基类的虚函数时虚函数的返回值类型不同,基类返回基类的指针或引用,派生类返回派生类的指针或引用。
(2)析构函数重写(基类与派生类的析构函数名不同),如果基类的析构函数为虚函数,此时派生类析构函数只要定义,虽然名字不同但仍是虚函数的重写,因为编译器会将析构函数的名称统一处理为destructor。
4>函数重载、隐藏(重定义)和覆盖(虚函数重写)的对比
语法 | 重载 | 隐藏 | 覆盖(虚函数重写) |
---|
两函数作用域 | 相同 | 基类与派生类 | 基类与派生类 |
函数名 | 相同 | 相同 | 相同(析构函数除外) |
参数列表 | 不同 | 无要求 | 相同 |
返回值 | 相同 | 无要求 | 相同(协变除外) |
虚函数 | / | 无要求 | 两个函数必须是虚函数 |
调用要求 | 无 | 无 | 指针或者引用调用 |
重载:参数列表不同 | | | |
隐藏:子类重写父类中的代码,将父类中的内容隐藏 | | | |
覆盖:重写父类虚函数,包含多态。 | | | |
抽象类
1>概念及定义
包含纯虚函数的类叫抽象类(也叫接口),不能实例化出对象。纯虚函数是在虚函数的后面写上=0。派生类只有重写纯虚函数才能实例化出对象,纯虚函数规范了派生类必须重写,另外纯虚函数也体现出了接口继承。
2>实现继承和接口继承
普通函数的继承是实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
多态的原理
1>虚函数表(虚函数工作原理)
通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表,这个数组最后面放了一个nullptr。虚函数表中储存了为类对象进行声明的虚函数的地址。即有虚函数的指针将多包含一个指针,该指针指向该类中所有虚函数的地址表。
1.虚表存放规则
虚函数存放顺序按在类内的声明顺序存放的。vs下虚表存放在代码段。
2.派生类虚表构建规则
派生类拥有独立的虚函数表指针,派生类拷贝基类的虚表;若派生类重写了虚函数,则覆盖更新虚表;若派生类增加了新的虚函数,则将其放到最后。
2>多态的原理
即包含多态,接包含多态实例,如下
Person Father;
Student Child;
Func(Father);
Func(Child);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)