C++多态性
一、含义:
通过一个基类派生出的派生类,可以重载与基类形式完全相同(同函数名,同返回值类型,同参数列表)的函数,但是传入不同派生类对象的指针时,调用的是该派生类重载过的函数。
二、运算符重载
可以用类似函数声明和定义的语法形式(与函数定义基本相同,只是需要在操作符前面加上关键字operator)对C++的基本运算符针对某类对象进行重载(例如+、-、*、/、++、–),重载之后,在该类对象使用对应运算符时,会按照重载的运算去执行。
1.类内重载
当运算符的左操作数是是该类的对象时可以使用此种方法进行重载。 参数:当运算符是单目运算符时,无参数(当使用后置单目运算符时,参数里面写一个int形参就可以) 当操作数>=2, 第一个操作数不写(左操作数是类对象本身),只写后面的就可以 重载后,使用运算符相当于使用类对象调用对应的重载函数
例1:
class ComplexNum
{
public:
ComplexNum(int r, int v):real(r),vir(v){};
ComplesNum operator +(const Complex& c)
{
return ComplexNum(real + c.real, vir + c.vir);
}
private:
int real;
int vir;
}
例2:
class Clock
{
public:
Clock(int h, int m, int s):hout(h), min(m), sec(s)
{};
Clock& operator ++()
{
++sec;
if (sec >= 60)
{
sec -= 60;
min++;
if (min >= 60)
{
min -=60;
hour = (hour+1)%24;
}
}
return *this;
}
private:
int hour;
int min;
int sec;
}
2.类外重载
当运算符的左操作数不是该类的对象时,不能在类内重载该运算符,只能在类外重载该运算符; 或者当类不是我们自己写的时候,不能在类内重载该运算符,只能在类外重载该运算符;
使用这种方法时,一般需要将函数设置成类的友元,否则无法直接访问私有成员,需要使用成员函数访问,会比较复杂。
同时,使用这种方法时参数列表需要写出全部的操作数
例1:重载复数类的加法
class ComplexNum
{
public:
ComplexNum(int r, int v):real(r),vir(v){};
ComplexNum operator +(const Complex& c)
{
return ComplexNum(real + c.real, vir + c.vir);
}
friend ComplexNum operator +(const ComplexNum& c1, const ComplexNum& c2);
private:
int real;
int vir;
}
ComplexNum operator +(const ComplexNum& c1, const ComplexNum& c2)
{
return ComplexNum(c1.real + c2.real, c1.vir + c2.vir);
}
例2:重载<<操作符,从而可以直接使用cout输出复数类对象
class ComplexNum
{
public:
ComplexNum(int r, int v):real(r),vir(v){};
ComplexNum operator +(const ComplexNum& c)
{
return ComplexNum(real + c.real, vir + c.vir);
}
friend ComplexNum operator +(const ComplexNum& c1, const ComplexNum& c2);
friend ostream& operator <<(ostream& out, const ComplexNum& c);
private:
int real;
int vir;
}
ostream& operator <<(ostream& out, const ComplexNum& c)
{
out << "(" << c.real << "," << c.vir << ")";
return out;
}
三、虚函数
1.虚函数定义
在定义基类时,把要在派生类重载的函数定义为虚函数(使用virtual关键词修饰),在派生类同样使用virtual关键词修饰该函数,然后使用基类指针去调用该函数,当传入的指针为某个派生类的指针时,会在运行时根据指针类型动态绑定实际调用的虚函数为该派生类重载的那个虚函数。
当定义为虚函数是,该函数的函数体不能写在类体内(类体内的一般是内联函数,编译时就绑定了)。
virtual关键字只在类内声明时添加,在类体外实现时不用添加。
例:
class Base
{
public:
virtual void display();
};
void Base::display()
{
cout << "Base1 display" << endl;
};
class Derive:public Base
{
public:
virtual void display();
};
void Derive::display()
{
cout << "Derive display" << endl;
};
void fun(Base* b)
{
b->display();
};
int main()
{
Base b;
Derive d;
fun(&b);
fun(&d);
return 0;
}
2.虚函数特征
- 使用virtual关键词修饰的函数
- 虚函数是实现运行时多态的基础
- C++虚函数是动态绑定的函数
- 虚函数必须是非静态成员函数,虚函数经过派生之后,就可以实现运行中的多态
什么函数可以是虚函数? 1.一般的成员函数 2.构造函数不能是虚函数 3.析构函数可以是虚函数
3.virtual关键字相关
- 派生类的虚函数可以不写virtual关键字(但是建议都写上),系统会根据函数原型进行判断是否为虚函数(返回值、函数名、参数表)
- 派生类的虚函数会隐藏基类同名函数的其他所有重载形式
- 一般派生类的函数中也加virtual关键字,提高代码可读性
4.虚析构函数
如果打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数为虚析构函数,否则执行delete的结果是不正确的。
不声明析构函数为虚析构函数时:
class Base
{
public:
~Base();
};
Base::~Base()
{
cout << "Base destruction function" << endl;
};
class Derive :public Base
{
public:
~Derive();
private:
int* p;
};
Derive::~Derive()
{
delete p;
cout << "Derive destruction function" << endl;
};
void fun(Base* b)
{
delete b;
};
int main()
{
Base* d = new Derive();
fun(d);
return 0;
}
声明析构函数为虚析构函数时:
class Base
{
public:
virtual ~Base();
};
Base::~Base()
{
cout << "Base destruction function" << endl;
};
class Derive :public Base
{
public:
virtual ~Derive();
private:
int* p;
};
Derive::~Derive()
{
delete p;
cout << "Derive destruction function" << endl;
};
void fun(Base* b)
{
delete b;
};
int main()
{
Base* d = new Derive();
fun(d);
return 0;
}
5.虚表
每个类对象在创建时,都隐含有一个虚指针(指向该类所有虚函数的一个虚表),运行时,通过对象的虚指针,找到对应的虚表,然后通过虚表里面的函数指针,找到对应的函数进行执行,实现动态绑定。
6.抽象类(具有纯虚函数的类)
纯虚函数的语法是虚函数后面添加=0,没有函数体。
具有纯虚函数(只要有1个就是)的类,称为抽象类,抽象类只能作为基类使用,不能创建实例对象,当派生类实现了该虚函数时,才能使用该派生类创建实例对象。
抽象类的意义: 将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类实现
7.override和final
7.1 override
当在派生类重载某个函数以覆盖基类的函数时,需要派生类的函数原型与基类的函数原型完全相同才能实现覆盖,如果派生类函数的原型与基类稍有不同,则可能无法实现覆盖,然后语法有没有问题,无法在编译时识别错误。运行时出错很难调试定位错误位置。此时可以使用override对函数进行显示的覆盖说明,当用override修饰时,派生类的函数原型必须在基类找到与之完全对应的一个函数原型,否则就会提示编译错误,这样在写代码的时候就可以发现该错误进行修正。
class Base0
{
public:
virtual void display(int a);
};
class Derive0:public Base0
{
public:
virtual void display();
};
class Base0
{
public:
virtual void display(int a);
};
class Derive0:public Base0
{
public:
virtual void display() override;
};
7.2 final
当类不想被继承,或者函数不想被重载时,可以在类名后面加上final限制
final限定类:
class Base final
{};
class Derive :public Base
{};
final限定函数:
class Base2
{
public:
virtual void display() final;
};
class Derive2 :public Base2
{
public:
virtual void display();
};
|