C++的三大特性之一
分类:
有了虚函数, 基类指针指向基类对象时就使用基类成员(包括变量和函数),指向派生类对象时就使用派生类的成员。即, 基类指针可以按照基类的方式做事,也可以按照派生类的方式做事,它有多种形态, 也称为“多态”
虚函数的唯一用处就是构成多态
C++ 提供多态的目的就是 可以通过基类指针对所有派生类(包括直接派生或间接派生)的成员变量和成员函数进行“全方位” 的访问, 尤其是成员函数。如果没有多态,我们就只能访问成员变量
虚函数的注意事项
1)只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可加
2) 为了方便,只需要将基类中的函数声明为 虚函数 即可, 这样所有派生类中具有遮蔽关系的同名函数都将自动成为 虚函数。
3) 只有当派生类的虚函数覆盖基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。例如基类虚函数的原型为 virtual void func();派生类虚函数的原型为virtual void func(int);那么当基类指针 p 指向派生类对象时, 语句 p->func(100),将出错
构成多态的条件
1)必须存在继承关系
2)继承关系中必须有同名的虚函数, 并且它们是覆盖关系(函数原型相同)
3)存在基类指针, 通过该指针调用虚函数
区别:
#include
using namespace std;
class Animal
{
public:
void speak()
{
cout <<"动物说话"<< endl;
}
};
class cat :public Animal
{
public:
void speak()
{
cout << "猫在说话" << endl;
}
};
void dospeak(Animal &animal)
{
animal.speak();
}
void test()
{
cat cat;
//使用子类调用本来参数是父类的函数,也可以,但是此时返回的值任然会是父类中的函数在执行
//这是由于这种操作属于早绑定,编译阶段函数的地址就已经确定了
dospeak(cat);
}
int main()
{
test();
}
以上的情况,使用子类调用本来参数是父类的函数,也可以,但是此时返回的值任然会是父类中的函数在执行
这是由于这种操作属于早绑定,编译阶段函数的地址就已经确定了。
而如何改变上述情况,使得使用子类对象能调用包含父类参数的函数:
即:想让 cat 类执行,那么这个函数地址就不能提前绑定好,需要在运行阶段进行绑定,地址晚绑定。
解决:使用虚函数:
class Animal
{
public:
//虚函数
virtual void speak()
{
cout <<"动物说话"<< endl;
}
};
class cat :public Animal
{
public:
void speak()
{
cout << "猫在说话" << endl;
}
};
解释:
此时,当父类的函数声明了虚函数后,当子类需要调用自己与父类同名函数时,就会忽略父类的虚函数,使用相应子类的成员函数。
动态多态的满足条件:
1、有继承关系
2、子类重写父类的虚函数(即与父类的虚函数的函数声明完全相同,子类不加virtual)
动态多态的使用:
即:父类的指针或者引用 ,执行子类对象
class cat :public Animal
{
public:
void speak()
{
cout << "猫在说话" << endl;
}
};
void dospeak(Animal &animal) // Animal &animal = cat
{
animal.speak();
}
void test()
{
cat cat;
dospeak(cat);//这里
}
class Animal
{
public:
void speak()
{
cout <<"动物说话"<< endl;
}
};
首先:
在没有 virtual
关键字时,此时该类占用的是 1个字节
当加上 virtual
关键字后,此时该类占用的是4 个字节( )
(无论是什么指针都占4个字节)
class Animal
{
public:
//虚函数
virtual void speak()
{
cout <<"动物说话"<< endl;
}
};
多态的优点:
应用场景:
(1)针对 真正开发中,对于需要对函数进行扩展新的功能时,尽量不修改原来的代码,只进行扩展开放,修改关闭的原则
案例:
//先定义一个计算器的基类(抽象基类)
class AbcCalculater
{
public:
virtual int calculater()
{
return 0;
}
int M_A;
int M_B;
};
//扩展加法运算
class ADDCalculater :public AbcCalculater
{
public:
virtual int calculater()
{
return M_A + M_B;
}
};
class SubCalculater :public AbcCalculater
{
public:
virtual int calculater()
{
return M_A - M_B;
}
};
void test()
{
enum operation
{
add,//枚举默认从0开始
sub
};
AbcCalculater * add_;
operation oper(add);
switch (oper)
{
case operation::add:
add_ = new ADDCalculater;//使用指针方式创建堆区给计算器的子类的加法类
add_->M_A = 10;
add_->M_B = 10;
cout << add_->calculater() << endl;
delete add_;//创建堆区,必须手动销毁
break;
case operation::sub:
add_ = new SubCalculater;//使用指针方式创建堆区给计算器的子类的减法类
add_->M_A = 10;
add_->M_B = 10;
cout << add_->calculater() <<endl;
delete add_;//创建堆区,必须手动销毁
break;
default:
break;
}
}
纯虚函数:
本身父类中的虚函数基本不会被调用,所以父类中的虚函数没有必要进行定义
只进行声明,对虚函数内部不进行定义,而在子类中才进行定义
virtual int calculater() = 0 ;//只进行声明,对虚函数内部不进行定义,而在子类中才进行定义
抽象类:
特点:
举例:
class Base
{
public:
virtual int func() = 0;//定义纯虚函数
};
class child_1 :public Base
{
public:
int func()
{
cout <<"儿子1"<< endl;
}
};
void test4()
{
//Base b;//包含纯虚函数,属于抽象类,不能实例化对象
Base * b;
b = new child_1;
b->func();
delete(b);//创建了堆区就必须手动释放堆区
//或者
child_1 son;
son.func();
}
class Drink
{
public:
virtual void zhushui() = 0;
virtual void jiachaye() = 0;
virtual void chongpao() = 0;
virtual void jiazuoliao() = 0;
void make_drink() //该函数会被继承到子类
{
zhushui();
jiachaye();
chongpao();
jiazuoliao();
}
};
class Make_tea: public Drink
{
public:
void zhushui()
{
cout <<"煮水" << endl;
}
void jiachaye()
{
cout << "加茶叶" << endl;
}
void chongpao()
{
cout <<"冲泡" <<endl;
}
void jiazuoliao()
{
cout << "加作料" << endl;
}
};
void dowork(Drink *s)
{
s->make_drink();
delete(s);
}
void test()
{
Drink *s = new Make_tea;//实例化子类,调用子类的make_drink()函数
dowork(s);
}
多态在使用时,如果子类中有属性开辟了堆区,那么父类指针在释放时无法调用到子类的析构函数
——解决方法:将父类中的析构函数改成 虚析构 或者 纯虚析构
虚析构 和 纯虚析构 共性:
虚析构 和 纯虚析构 的区别:
虚析构例子:
class Animal
{
public:
Animal()
{
cout <<"animal的构造" <<endl;
}
virtual ~Animal()//此时使用了虚析构这个方式,系统就会先去释放子类的虚构,再来释放父类这个析构
{
cout << "animal 的析构" << endl;
}
virtual void speak() = 0;
};
class cat :public Animal
{
public:
cat(string s)
{
cat_name = new string(s);//使用堆区去维护这个名字
cout <<"猫的构造" << endl;
}
~cat() // 需要注意的是,如果父类的析构不使用虚析构,在使用的父类进行子类实例化对象后,子类的这个析构是不会被调用的
{
cout << "调用cat析构" << endl;
if (cat_name != NULL)
{
delete(cat_name);
cat_name = NULL;
}
}
void speak()
{
cout <<*cat_name << "在说话" << endl;
}
string *cat_name;
};
void test_work()
{
Animal *s = new cat("Tom");
s->speak();
delete(s);
}
结果:
animal的构造
猫的构造
Tom在说话
调用cat析构
animal 的析构
virtual ~Animal()//此时使用了虚析构这个方式,系统就会先去释放子类的虚构,再来释放父类这个析构
{
cout << "animal 的析构" << endl;
}
class Animal
{
public:
Animal()
{
cout <<"animal的构造" <<endl;
}
//virtual ~Animal()//此时使用了虚析构这个方式,系统就会先去释放子类的虚构,再来释放父类这个析构
//{
// cout << "animal 的析构" << endl;
//}
virtual ~Animal() = 0;//纯虚析构(方便定义)
virtual void speak() = 0;
};
Animal::~Animal()
{
cout << "animal 的纯虚析构" << endl;
}