目录
一、前言
多态作为C++三大特性之一,在C++中具有十分重要的作用。
那么今天我们就来讲一讲多态。
二、多态的概念
多态:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态。 为不同数据类型的实体提供统一的接口。
三、虚函数
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
四、虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
即:三同:函数名、参数,返回值均相同。
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person
{
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
}
注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)。但是为了规范,建议加上。
五、多态的定义与实现
1、定义
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。在继承中要构成多态还有两个条件:1、必须通过父类的指针或者引用调用虚函数。2、 子类必须对父类的虚函数进行重写。
2、实现
class Person
{
public:
virtual void BuyTicket() { cout << "买票——全价" << endl; }
};
class Student :public Person
{
public:
virtual void BuyTicket() { cout << "买票——半价" << endl; }
};
class Soldier :public Person
{
public:
virtual void BuyTicket() { cout << "优先买票" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
Soldier sd;
Func(p);
Func(s);
Func(sd);
return 0;
}
代码的运行结果如下:
3、构成多态条件的理解
必须通过父类的指针或者引用调用虚函数。如下:
我们发现如果不是父类的引用的话就无法构成多态了。
子类必须对父类的虚函数进行重写。
class Person
{
public:
virtual void BuyTicket() { cout << "买票——全价" << endl; }
};
class Student :public Person
{
public:
};
class Soldier :public Person
{
public:
};
void Func1(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
Soldier sd;
Func1(p);
Func1(s);
Func1(sd);
return 0;
}
运行结果如下:
从上面我们可以看出要实现多态就必须在子类中重写虚函数。
六、协变
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。如下图:
class A
{};
class B : public A
{};
class Person
{
public:
virtual A* f() {return new A;}
};
class Student : public Person
{
public:
virtual B* f() {return new B;}
};
七、抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
class BMW :public Car
{
public:
virtual void Drive()
{
cout << "BMW-操控" << endl;
}
};
八、实现继承与接口继承
先让我们看一看下面的例子,下面代码的运行结果是什么呢?
class A
{
public:
virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
virtual void test(){ func();}
};
class B : public A
{
public:
void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
B*p = new B;
p->test();
return 0;
}
代码运行结果是 B->1。为什么呢?这就与我们的实现继承和接口继承有关了。
1、实现继承
2、接口继承
九、多态的原理
1、虚函数表
2、同一类型的对象共用同一个虚表
3、 调用原理
我们使用下面的代码来看看多态的调用原理
class Person
{
public:
virtual void buy()
{
cout << "1";
}
};
class Student :public Person
{
public:
virtual void buy()
{
cout << "2";
}
};
从上面的图中我们可以看出不同的对象会去从自己的虚函数表中调用自己实现的虚函数。这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
4、动态绑定与静态绑定
十、重载、重写和隐藏的区别
1、重载
两个函数在同一作用域。
函数名相同,参数不同(类型、顺序个数)。
2、重写(覆盖)
两个函数分别在基类和派生类的定义域。
函数名/参数/返回值都相同(协变除外)。
两个函数必须是虚函数。
3、隐藏(重定义)
两个函数分别在基类和派生类的作用域。
函数名相同。
两个基类和派生类的同名函数不构成重写就是重定义。
十一、多继承中的虚函数表
我们知道单继承中的子类中只有一张虚函数表,那么多继承中的子类的虚函数表是什么样的呢?
我们通过把虚函数表的地址打印出来,来观察一下多继承中虚函数表是什么样子的。
class Base1
{
public:
virtual void func1() {cout << "Base1::func1" << endl;}
virtual void func2() {cout << "Base1::func2" << endl;}
private:
int b1;
};
class Base2
{
public:
virtual void func1() {cout << "Base2::func1" << endl;}
virtual void func2() {cout << "Base2::func2" << endl;}
private:
int b2;
};
class Derive : public Base1, public Base2
{
public:
virtual void func1() {cout << "Derive::func1" << endl;}
virtual void func3() {cout << "Derive::func3" << endl;}
private:
int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR* table[])
{
cout << " 虚表地址>" << table << endl;
for (int i = 0; table[i] != nullptr; ++i)
{
printf(" 第%d个虚函数地址 :%p,->", i, table[i]);
VFPTR f = table[i];
f();
}
cout << endl;
}
int main()
{
Derive d;
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
PrintVTable(vTableb1);
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));
PrintVTable(vTableb2);
return 0;
}
运行结果如下:
观察上图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。