1、多态是指同样的消息(对类的成员函数的调用)被不同类型的对象接受时导致的不同的行为(不同的实现,调用了不同的函数)
多态的分类:重载多态、强制多态(专用多态)、包含多态、参数多态(通用多态)
强制多态:将一个变元的类型加以变化,以符合一个函数或者操作的要求(例如,前面所讲的加法运算符在进行浮点数与整型数相加时,首先进行强制类型转换,把整型数变为浮点数再相加)
包含多态:类族中定义于不同类中的同名成员函数的多态行为,主要是通过虚函数来实现。
绑定:计算机程序自身彼此关联的过程,把一个消息和一个对象的方法相结合的过程。
静态绑定:绑定工作在编译连接阶段完成的情况。
动态绑定:绑定工作在程序运行阶段完成的情况。
2、运算符重载(对已有的运算符赋予多重含义,使同一个运算符用于不同的数据时导致不同的行为)
一些操作符不能被重载: . .* :: ?:
运算符重载为类的成员函数的一般语法为:
返回类型 类名::operator 运算符(形参表)
{
函数体
}
运算符重载为非成员函数的一般语法为:
返回类型 operator 运算符(形参表)
{
函数体
}
提示:当以非成员函数形式重载运算符时,有时需要访问运算符参数所涉及类的私有成员,这时可以把该函数声明为类的友元函数
如果是双目运算符(B),左操作数是对象本身的数据,由this指针指出,右操作数则需要通过运算符重载函数的参数表来传递 要实现oprd1 B oped2,其中oprd1为A类的对象,则应该把B重载为A类的成员函数,该函数只有一个形参,形参的类型是oprd2所属的类型,重载后,该表达式就相当于调用oprd.operator B(oprd2)
如果是单目运算符,操作数由对象的this指针给出,不需要任何参数
U oped,其中oprd为A类对象,则U应该重载为A类的成员函数,函数没有形参,经过重载后,该表达式相当于oprd.operator U()
如果是后置运算符,例如oprd++,oprd--,其中oprd是A类的对象,那么运算符就应当是重载为A类的成员函数,该函数带有一个int形参,相当于oprd.operator++(0)
3、一个复数重载运算的例子
#include <iostream>
using namespace std;
class Complex{
public:
Complex(double r=0.0,double i=0.0):real(r),imag(i){}
Complex operator+(const Complex &c2) const;
Complex operator-(const Complex &c2) const;
void display() const;
private:
double real;
double imag;
};
Complex Complex::operator+(const Complex &c2) const{
return Complex(real+c2.real,imag+c2.imag);
}
Complex Complex::operator-(const Complex &c2)const{
return Complex(real-c2.real,imag-c2.imag);
}
void Complex::display()const{
cout<<"("<<real<<","<<imag<<")"<<endl;
}
int main(){
Complex c1(5,4),c2(2,10),c3;
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
c3=c1-c2;
cout<<"c3=c1-c2=";c3.display();
c3=c1+c2;
cout<<"c3=c1+c2=";c3.display();
return 0;
}
4、重载运算符函数实现最后要加上
ostream & operator<<(ostream &out,const Complex &c){
out<<"("<<c.real<<","<<c.imag<<")";
return out;
}
(承接上面的复数运算的例子)
ostream是cout类型的一个引用,"<<"操作符的左操作数为ostream的引用,ostream是cout类型的一个基类,右操作数是Complex类型的引用,这样子啊执行cout<<c1时,就会调用operator<<(cout,c1)
要重载的操作符的第一个操作数不是可以更改的类型,例如"<<"运算符的第一个操作数的类型为ostream,是标准库的类型,无法向其中添加成员函数。
5、虚函数是动态绑定的基础,必须是非静态的成员函数,经过派生后,在类族中就可以实现运行过程的多态。
一般虚函数成员的声明语法是:
virtual 函数类型 函数名(形参表);
这实际上就是在类的定义中使用virtual关键字来限定成员函数,虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。
运行过程中的多态需要满足3个条件,首先类之间满足赋值兼容规则,其二是要声明虚函数,第三是要由成员函数来调用或是通过指针、引用来访问虚函数。 如果是使用对象名来访问虚函数,则绑定在编译过程中就可以进行(静态绑定),而无须在运行过程中进行(虚函数一般不声明为内联函数,但也不会引起错误)
用指向派生类对象的指针仍然可以调用基类中被派生类覆盖的成员函数,方法是使用::进行限定。
通过一个指向派生类对象的基类指针,可以访问到派生类的虚函数,但缺省形参值是静态绑定的,只能来自基类的定义。
6、用派生类对象复制构造基类对象的行为称为对象切片,类型与派生类对象一致。
7、使用override的情况:虚函数、基类中要有该函数。
例如:void f1(int) const override;
使用final的情况:该函数不能被覆盖,任何试图覆盖该函数的操作都将引发错误。
例如:void f1(int) const final; //不允许后续的其他类覆盖f1(int)
8、虚析构函数
virtual ~类名();
如果一个类的析构函数是虚函数,那么由他派生而来的所有子类的析构函数也是虚函数。析构函数设置为虚函数之后,在使用指针饮用时可以动态绑定,实现运行时的多态,保证使用基类类型的指针能够调用适当的析构函数。
简单来数,如果有可能通过基类指针调用对象的析构函数(通过delete),就需要让基类的析构函数称为析构函数,否则会产生不确定的后果。(通过基类指针删除派生类对象时调用的是基类的析构函数,派生类的析构函数没有被执行,因此派生类对象中动态分配的内存空间没有得到释放,造成了内存泄露)
9、纯虚函数(对于在基类中无法实现的函数,可以在基类中只说明函数原型用来规定整个类族的统一接口形式,在派生类中再给出函数的具体实现)
声明格式为:
virtual 函数类型 函数名(参数表)=0;
实际上它与一般虚函数的原型在书写格式上的不同就在于后面加了“=0“。声明为纯虚函数之后,基类中就可以不再给出函数的实现部分。(基类中仍然允许对纯虚函数给出实现,但即使给出实现,也必须由派生类覆盖,否则无法实例化)
如果将析构函数声明为纯虚函数,必须给出它的实现,因为派生类的析构函数体执行完后需要调用基类的纯虚函数。
10、抽象类是带有纯虚函数的类。建立抽象类是为了通过它多态地使用其中的成员函数,一个抽象类自身无法实例化,也就是说我们无法定义一个抽象类的对象,只能通过继承机制,生成抽象类的非抽象派生类,然后再实例化。
例子:
#include <iostream>
using namespace std;
class Base1{
public:
virtual void display() const=0; //纯虚函数
};
class Base2:public Base1{ //公有派生类Base2定义
public:
void display() const; //覆盖基类的虚函数
};
void Base2::display() const{
cout<<"Base2::display()"<<endl;
}
class Derived:public Base2{
public:
void display() const; //覆盖基类的虚函数
};
void Derived::display()const{
cout<<"Derived::display()"<<endl;
}
void fun(Base1 *ptr){ //参数为指向基类对象的指针
ptr->display(): //“对象指针->成员名”
}
int main(){
Base2 base2;
Derived derived;
fun(&base2); //用Base对象的指针调用fun函数
fun(&derived): //用Derived对象的指针调用fun函数
return0;
}
11、深度探索
C++的类类型分为两类--多态类型和非多态类型。多态类型是指有虚函数的类类型,非多态类型是指所有的其他类型。
设计多态类型的一个重要原则是,把多态类型的析构函数设定为虚函数,例如下面的程序(Bread和Icecream都是Food的派生类):
Food *f;
if(weatherIsHot())
f=new Ice cream();
else
f=new Chocolate();
eat(f);
delete f;
对非多态类的公有继承,应当慎重,而且一般没有太大必要。
如果一个函数的执行方式十分明确,不需要任何特殊处理,不希望派生类提供特殊的实现,就应将它声明为非虚函数。如果一个类的所有函数都具有这个特点,就把这个类作为非多态类型。将一个类型设计为非多态类型,一般意味着不希望其他类对它进行公有继承。换句话说,如果需要其他类对其进行公有继承,则应将其设计为多态类型。
12、dynamic_cast是与static_cast、const_cast、reinterpret_cast并列的四种类型转换操作符之一,他可以将基类的指针显式转换为派生类的指针,或将基类的引用显式转换为派生类的引用。但与static_cast不同的是,它执行的不是无条件的转换,它在转换前会检查指针(或引用)所指向的对象的实际类型是否与转换的目的类型兼容,如果兼容转换才会发生,才能得到派生类的指针(或引用)所指向对象的实际类型是否与转换的目的类型兼容,如果兼容转换才会发生,才能得到派生类的指针(或引用)。