本篇目录
- 前言
- 1.多态的概念
- 1.1概念
- 2.多态的定义及实现
- 2.1 虚函数
- 2.2 虚函数的重写
- 2.3 多态的构成条件
- 3.一道经典题目
- 4.多态的原理
- 4.1虚函数表
前言
需要声明的,这两篇文章(C++之多态(上下篇))的运行环境都是在vs2013下的x86程序中,涉及的指针都是4bytes。如果要在其他平台下,部分代码需要改动。比如:如果是x64程序,则需要考虑指针是8bytes问题等等。
1.多态的概念
1.1概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举个例子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时,是优先买票。
2.多态的定义及实现
2.1 虚函数
虚函数:即被virtual修饰的类成员函数称为虚函数。
ps: 普通函数不能用virtual修饰,会报错。
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
2.2 虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数。(需要满足三同:即派生类虚函数与基类虚函数的返回值类型,函数名字,参数列表完全相同,不符合重写就是隐藏关系),称派生类的虚函数重写了基类的虚函数。
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student :public Person
{
public:
//虚函数重写/覆盖条件:虚函数+三同(函数名、参数、返回值)
//不符合重写,就是隐藏关系
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
虚函数重写的两个例外:
2.3 多态的构成条件
多态是在不同继承关系的类对象,去调用同一个函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。
在继承中构成多态还要满足两个条件:
1.必须通过基类的指针或者引用去调用虚函数
2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student :public Person
{
public:
//虚函数重写/覆盖条件:虚函数+三同(函数名、参数、返回值)
//不符合重写,就是隐藏关系
//**特例1:子类虚函数不加virtual,依旧构成重写(实际最好加上)**
//void BuyTicket()
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
class Soldier :public Person
{
public:
virtual void BuyTicket()
{
cout << "优先买票" << endl;
}
};
//多态的两个条件
//1、虚函数重写
//2、父类指针或者引用去调用虚函数
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Soldier sd;
Func(ps);
Func(st);
Func(sd);
return 0;
}
特例1:子类虚函数不加virtual,依旧构成重写(实际最好加上)
特例2:重写的协变,返回值可以不同,要求必须是父子关系的指针或者引用,这里的父子关系不一定是此父类和子类,其他的父子关系都可以
class Person
{
public:
virtual Person* BuyTicket()
{
cout << "买票-全价" << endl;
return this;
}
};
class Student :public Person
{
public:
//虚函数重写/覆盖条件:虚函数+三同(函数名、参数、返回值)
//不符合重写,就是隐藏关系
virtual Student* BuyTicket()
{
cout << "买票-半价" << endl;
return this;
}
};
class A
{};
class B :public A
{};
class Person
{
public:
virtual A* BuyTicket()
{
cout << "买票-全价" << endl;
return nullptr;
}
};
class Student :public Person
{
public:
//虚函数重写/覆盖条件:虚函数+三同(函数名、参数、返回值)
//不符合重写,就是隐藏关系
virtual B* BuyTicket()
{
cout << "买票-半价" << endl;
return nullptr;
}
};
3.一道经典题目
问:以下程序的输出结果是什么?
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;
}
解析:p是一个指向B对象的指针,p通过指针的方式去访问B继承A的成员函数test,成员函数放在公共代码区,
所以p是一个B的指针,当它调用对象A中的成员函数test时,test的this指针是指向A对象的,所以这里将B
传给A*会发生切片或者切割,test函数里面的func()函数,this->func();这里的this是p的同级拷贝,这里的
this指向的是B,通过切片后看到的是A,这里满足多态的两个条件,所以这里是多态,指向谁就调用谁,这里指向的
是B,则调用B对象的func()函数,我们知道子类的虚函数是可以不加virtual的,虚函数重写是接口继承,即前面那部分与父类
保持一致,所以这里的val是父类的缺省值1而不是子类的缺省值0,重写实现,实现部分是调用子类自己的,所以答案是B->1。
4.多态的原理
4.1虚函数表
这里有一道笔试题,计算sizeof(Base)是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
cout << sizeof(Base) << endl;
return 0;
}
通过观察测试我们发现答案是8bytes,除了_b成员,还多了一个_vfptr指针放在对象的前面,(注意平台可能放在对象的最后面,跟平台有关),对象的这个指针我们叫做虚函数表指针,v表示virtual,f表示function;一个含有虚函数成员的类都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表简称虚表,那么派生类中这个表放了些什么呢,我们接着往下分析。
总结:
多态的本质原理,符合多态的两个条件,那么调用时,会到指向对象的虚表中找到对应的虚函数地址,进行调用。
下篇接这里继续,大家可以休息一下哦。