多态
多态
实现同一方法的调用,完成不同的效果,方便程序的接口实现
一、动态联编和静态联编
1、什么是联编
1.联编指的是:计算机自身彼此关联的过程,在这个过程中确定程序中的操作调用与之该操作的代码之间的映射关系,比如:函数重载时,编译器会根据函数名和函数的参数来区别要执行的函数(那一段代码)。这就是建立的一种映射关系,明确函数调用时会执行哪一个代码段
2.静态联编:指的是联编工作出现在写代码的阶段,又称之为早期联编,或静态约束。在编译时就确定了函数实现及函数调用的关联。比如C语言的函数,只需要函数名和参数就能确定能不能调用函数,因为C语言是没有函数重载,参数正确才能调用。
2.动态联编:指的是编译过程不能确定知道将要调用的函数,只能在程序运行的时候才能确定将要调用的函数,也就是说在程序运行的时候才将函数实现和函数调用关联,也叫晚期联编,或动态约束
2、实现动态联编的条件
1.必须把动态联编的行为定义为类的虚函数
2.必须有类,而且类之间满足父子关系,这个行为通常是重写的父类的虚函数
3.必须是先试用基类指针或引用指向派生类对象,然后使用基类指针调用虚函数成员
注:其实动态联编就是今天学习的多态
二、多态
1、多态的概念
1.多态:字面意思就是具有多种形式或者状态,理解为,同一个接口,通过不同的对象调用就有不同的效果
2、虚函数
1.类中声明函数成员的时候,在函数的前面加上virtual关键字,则该成员为虚函数。
virtual 函数返回值类型 函数名(参数列表){函数体;}
2.虚函数的作用
通过累的继承及函数来实现的多态
3、虚函数的特点
1.如果在类中定义的虚函数,那么系统会为这个类维护一个虚函数表
2.类中会多出4个字节的指针去指向这个虚函数表,在虚函数表中保存了虚函数的首地址,在调用虚函数的时候就会先到表中查找虚函数,然后调用,就比普通函数多了步操作
3.虚函数表不会被继承,但是表中的项会被继承(虚函数会被继承)
4、通过类的继承和虚函数实现多态
1.有两个类,他们之间是父子关系
2.子类和父类有同名的虚函数,但是功能不同
3.父类的指针或引用指向父类或子类对象,指向那个对象就可以调用那个对象的虚函数成员
注意事项:
1.指针指向一个子类对象的地址(对指针解引用得到对象)
2.引用指向一个子类对象
3.通过基类指针指向子类对象在调用这个虚函数成员的时候,指向哪个对象就调用那个对象里的成员
4.通常在子类也会带上这个virtual关键字(也可以不带)
5.如果在派生类中没有重写基类的虚函数(重新定义虚函数的功能),那么就算满足虚函数的要求,调用的还是基类的虚函数成员(所以子类中不重写虚函数的功能,那就没有意义了)
6.通过基类指针指向派生类对象所能调用的只是虚函数成员,其他的都不能调用
#include<iostream>
using namespace std;
class animal
{
public:
virtual void eat()//如果去掉virtual,不是虚函数,那么fun函数里指向的是animal的eat函数那就一定是指向的是animal的eat函数,而不是cat的eat函数
{
cout << "动物在吃东西" << endl;
}
};
class cat :public animal
{
public:
void eat()
{
cout << "猫在吃东西" << endl;
}
};
void fun(animal* animal)
{
animal->eat();
}
void fun1(animal&p)
{
p.eat();
}
int main()
{
animal p4;
cat p3;
fun1(p4);//动物在吃东西
fun1(p3);//猫在吃东西
animal* p2 = new animal;
fun(p2);//动物在吃东西
delete p2;
p2 = NULL;
cat* p1 = new cat;
fun(p1);//猫在吃东西
delete p1;
p1 = NULL;
//animal* p = new cat;//实例化了一个猫对象
//p->animal::eat();//调用的应该是猫对象里的eat函数
//delete p;
//p = NULL;
system("pause");
return 0;
}
虚函数的构造析构:
#include<iostream>
using namespace std;
class animal
{
public:
animal()
{
cout << "基类构造" << endl;
}
~animal()
{
cout << "基类析构" << endl;
}
virtual void eat()
{
cout << "动物在吃东西" << endl;
}
};
class dog :public animal
{
public:
dog()
{
cout << "派生类构造" << endl;
}
~dog()
{
cout << "派生类析构" << endl;
}
void eat()
{
cout << "狗在吃东西" << endl;
}
};
int main()
{
animal* p1 = new dog;
//p1->eat();
delete p1;
p1 = NULL;//因为只是释放了基类对象,只有基类对象死亡,也只创建了派生类对象,但是没有死亡
//基类构造
//派生类构造
//基类析构
//将基类的析构函数变为虚析构,那么在释放的时候就会调用派生类的析构函数
system("pause");
return 0;
}
6、多态下释放需要注意:
1.基类中有虚函数时,且是通过基类指针去分配派生类对象的时候,在释放的时候只能通过基类指针去进行释放,在delete基类指针的时候会调用基类的析构函数,而不会调用派生类的析构函数,需要把基类的析构函数定义为虚析构,那么在释放基类指针的时候也就会调用子类的析构函数了
三、纯虚函数
1、纯虚函数的概念
1.纯虚函数是一种特殊的虚函数。在基类中不能给虚函数有意义的实现,就可以把它声明为纯虚函数,然后把它的实现留给派生类完成
2.析构函数也可以是纯虚函数,但是需要在类外实现
virtual ~animal()=0;//类中声明
2、纯虚函数的定义
virtual void fun()=0;
3、抽象类
1.在一个类中具有一个及以上的纯虚函数,那么这个类被称之为抽象类
2.抽象类不能实例化对象,但是可以定义指针,只能作为基类为派生类服务
3.如果派生类中没有完全实现基类中所有的纯虚函数,那么该派生类也会变成抽象类,同样不能实例化对象
#include<iostream>
using namespace std;
class animal
{
public:
virtual void eat() = 0;//定义纯虚函数
};
class cat :public animal
{
public:
virtual void eat()
{
cout << "猫在吃东西" << endl;
}
};
int main()
{
animal* animal = new cat;//不能new出animal
animal->eat();
delete animal;
animal = NULL;
system("pause");
return 0;
}
抽象类:描述具有共同属性的类,继承,只需要一个指针,一个接口
#include<iostream>
using namespace std;
class worker
{
public:
int a=10;
//公有的数据可以直接拿来用
void fun() { cout << "为了薪水" << endl; }
//这个fun函数就是他们公有的行为,而且这个行为还是一致的
virtual void speak() = 0;
virtual void work() = 0;
//用来给子类实现的,告诉你这些功能是你有的,而且需要做
};
//抽象类,把那些共有的数据和行为集合在一起,通过子类对象来继承使用
class yunfei :public worker
{
public:
virtual void speak()
{
cout << "通过麦给大家说话" << endl;
}
virtual void work()
{
cout << "坐在位子上工作" << endl;
}
};
class boss :public worker
{
virtual void speak()
{
cout << "面带微笑说话" << endl;
}
virtual void work()
{
cout << "老板自己才知道" << endl;
}
};
void fun(worker* worker)//传指针
{
worker->speak();
worker->work();
cout << worker->a << endl;
worker->fun();
}
void fun1(worker&worker)//直接传对象
{
worker.speak();
}
int main()
{
worker* p1 = new yunfei;
fun(p1);
delete p1;
p1=NULL;
system("pause");
return 0;
}