面向对象的特性---多态
-
多态
1.虚函数:在类的成员函数前加virtual即构成虚函数。作用是通过基类的指针与引用调用派生类的成员函数
2.多态的概念
多态即多种形态,通过调用不同的函数实现不同的功能。
如买票机制,不同的对象买票的制度也不同,如普通人买全票,学生可买半价票等。
3.多态的条件
(1)虚函数的重写(子类定义一个与父类的完全相同的虚函数)
(2)父类的指针或引用
当使用父类的指针或引用调用这个重写的虚函数时,指针指向父类就调用父类的虚函数,指向子类就调用子类的虚函数。
注意:
(1)基类(父类)必须为虚函数,派生类(子类)保持这一特性。(相当于父类必须为虚函数,子类可为虚函数也可不为虚函数)
(2)除协变外,派生类(子类)的重写必须保持函数名、参数、返回值与基类(父类)保持相同。
(3)协变就是派生类的中函数的返回值可以与基类的不同,但返回值必须是指向父子关系的指针或引用。
(4)静态成员函数不能为虚函数。因为静态成员函数就相当于是受命名空间限制的普通成员函数,可以把它看做是一个类。它与类的实例无关,在调用时不会调用隐含的this指针,因此不能为虚函数。简言之,成员函数实例相关,静态函数类相关,虚函数相当于成员函数,所以静态函数不能为虚函数。
(5)在类外定义虚函数,只能在声明时加virtual,不能在类外定义时加virtual。
(6)构造函数、拷贝构造函数、赋值运算符重载不能是虚函数。
原因:虚函数的作用是通过父类的指针或引用调用子类的相关函数,而构造函数在创建对象时是自己调用的,没有通过子类的指针或引用调用,所以构造函数不可能是虚函数。拷贝构造同理。
(7)析构函数要定义成虚函数---保证正确调用对应的函数
正常情况下,子类对象在构造时先调用父类的构造函数,再调用子类的构造函数;析构时先调用子类的析构函数,再调用父类的析构函数。可以理解为子
类有两部分,一部分是从父类继承的,一部分是自己定义的,初始化的时候先调用父类初始化从父类中继承的,再调用子类初始化自己定义的。析构函数也
是一样,子类的析构函数只会析构自己定义的一部分,若父类不定义成虚函数,在delete时只会调用父类的析构而不调用子类的析构,最终会导致内存泄漏
的问题。
4.普通调用&多态调用
普通调用:与类型有关,调用速度快
多态调用:与对象有关,调用速度较普通调用慢
-
继承体系同名成员函数的关系
1.重载:
同一作用域;
函数名相同;
参数(类型、个数)不同
void func()
{
std::cout << "func1()" << std::endl;
}
void func(int n)
{
std::cout << "func12()" << std::endl;
}
int main()
{
func();//func1
func(1);//func2
return 0;
}
2.重写(覆盖)
不同作用域(一个子类,一个父类);
函数名、参数、返回值相同(协变除外);
加virtual
class p1
{
public:
virtual void print()
{
std::cout << "p1 _a" << std::endl;
}
public:
int _a;
};
class p2:public p1
{
public:
virtual void print()
{
std::cout << "p2 _b" << std::endl;
}
public:
int _b;
};
void show(p1& p)
{
p.print();
}
int main()
{
p1 a;
p2 b;
a.print();//p1 _a
b.print();//p2_b
return 0;
}
3.重定义(隐藏)
不同作用域(一个子类,一个父类);
函数名相同;
子类或父类中不是重写的就是重定义
- 虚函数表分析
虚函数的指针有虚函数表,虚函数表相当于一个指针数组,里边存的是虚函数的函数指针 ,通常VS环境下以0结尾。
下图为子类和父类的虚函数基表:在32位机虚函数表是4字节,在64位机虚函数表是8字节
所以,上图中父类包含一个虚函数表以及一个整形变量,所以大小为8;子类在继承了父类的基础上自己还有一个整形变量,所以是12。