类和对象(多态)
多态
多条是C++面向对象的三大特性之一
多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指针或引用调用成员函数,执行不同的函数。
多态的分类
多态分为两种:编译时多态和运行时多态。
- 编译时多态:函数重载和运算符重载属于编译时多态,因为函数重载和运算符重载是静态的,函数地址早绑定。
- 运行时多态:虚函数和抽象类属于运行时多态,因为虚函数和抽象类是动态的,函数地址晚绑定。
案例:
//多态-1
#include <iostream>
using namespace std;
class Base
{
public:
void func()
{
cout<<"Base func"<<endl;
}
};
class Derived : public Base
{
void func()
{
cout << "Derived func()" << endl;
}
};
int main()
{
Base *p = new Derived();
p->func();
return 0;
}
运行结果:
Base func()
说明并未发生重写,而是发生了隐藏,因为在编译时,编译器会根据指针的类型来决定调用哪个函数,而不是根据指针所指向的对象的类型来决定调用哪个函数。
但是,如果将函数声明为虚函数,就可以实现运行时多态,如下所示:
//多态-2
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func()
{
cout << "Base func()" << endl;
}
};
class Derived : public Base
{
public:
void func()
{
cout << "Derived func()" << endl;
}
};
int main()
{
Base *q= new Base();
Base *p = new Derived();
Derived *d = new Derived();
q->func();
p->func();
d->func();
return 0;
}
Base func()
Derived func()
Derived func()
显然的,这里的多态是运行时多态,因为在运行时,编译器会根据指针所指向的对象的类型来决定调用哪个函数。
总结:
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
相关概念:重写:函数返回值类型 函数名 参数列表 完全一致称为重写
多态的实现
多态的实现有两种方式:虚函数和抽象类。
- 虚函数:虚函数是在基类中使用关键字virtual声明的函数,虚函数的特点是在运行时才动态绑定,可以实现运行时多态。
- 抽象类:抽象类是包含纯虚函数的类,抽象类的特点是不能实例化对象,只能作为接口使用。
多态的好处
多态的好处是提高了代码的扩展性和复用性。
- 提高了代码的扩展性:通过多态,可以在不修改原有代码的基础上,对原有代码进行扩展,增加新的功能。
- 提高了代码的复用性:通过多态,可以在不修改原有代码的基础上,对原有代码进行复用,增加新的功能。
多态的弊端
多态的弊端是降低了代码的可读性和可维护性。
- 降低了代码的可读性:通过多态,可以在不修改原有代码的基础上,对原有代码进行扩展,增加新的功能。
- 降低了代码的可维护性:通过多态,可以在不修改原有代码的基础上,对原有代码进行复用,增加新的功能。
虚函数
虚函数是在基类中使用关键字virtual声明的函数,虚函数的特点是在运行时才动态绑定,可以实现运行时多态。
虚函数的声明
虚函数的声明格式如下:
virtual 返回值类型 函数名(参数列表);
纯虚函数和抽象类
纯虚函数是在基类中使用关键字virtual声明的函数,并在函数后面加上=0,纯虚函数的特点是没有函数体,只有函数原型,不能被调用,只能被重写。
抽象类是包含纯虚函数的类,抽象类的特点是不能实例化对象,只能作为接口使用。
纯虚函数和抽象类的声明格式如下:
class 类名
{
public:
virtual 返回值类型 函数名(参数列表) = 0;
};
只要有纯虚函数的类,就是抽象类,抽象类不能实例化对象,只能作为接口使用。
使用时注意:
- 抽象类无法实例化对象
- 子类必须重写父类中的纯虚函数,否则也属于抽象类
示例:
//多态-3
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func() = 0;
};
class Derived : public Base
{
public:
void func()
{
cout << "Derived func()" << endl;
}
};
class Derived2 : public Base
{
public:
void func()
{
cout << "Derived2 func()" << endl;
}
};
int main()
{
Base *p = new Derived();
Derived2 d2;
p->func();
d2.func();
return 0;
}
Derived func()
Derived2 func()
子类重写父类中的虚函数,就可以实现运行时多态。
虚析构函数和纯虚析构函数
虚析构函数是在基类中使用关键字virtual声明的析构函数,虚析构函数的特点是在运行时才动态绑定,可以实现运行时多态。
纯虚析构函数是在基类中使用关键字virtual声明的析构函数,并在函数后面加上=0,纯虚析构函数的特点是没有函数体,只有函数原型,不能被调用,只能被重写。
虚析构函数和纯虚析构函数的声明格式如下:
virtual ~类名();
virtual ~类名() = 0;
使用纯虚析构的好处是可以防止内存泄漏,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。所以需要虚析构函数或者纯虚析构函数。
使用纯虚析构函数的时候同样不能实例化对象,只能作为接口使用。
虚析构示例:
//多态-4
#include <iostream>
using namespace std;
class Base
{
public:
virtual ~Base()
{
cout << "Base析构函数" << endl;
}
};
class Derived : public Base
{
public:
~Derived()
{
cout << "Derived析构函数" << endl;
}
};
void test01()
{
Base *p = new Derived();
delete p;
}
int main()
{
test01();
return 0;
}
Derived析构函数
Base析构函数
纯虚析构示例:
//多态-5
#include <iostream>
using namespace std;
class Base
{
public:
virtual ~Base() = 0;
};
Base::~Base()
{
cout << "Base析构函数" << endl;
}
class Derived : public Base
{
public:
int *p;
Derived()
{
p = new int(10);
}
~Derived()
{
cout << "Derived析构函数" << endl;
//释放堆区数据
if (p != NULL)
{
delete p;
p = NULL;
}
}
};
void test01()
{
Base *p = new Derived();
delete p;
}
int main()
{
test01();
return 0;
}
Derived析构函数
Base析构函数