多态
一、概念理解
要想理解C++中的多态,首先要理解虚函数
在类的成员函数前面加virtual关键字修饰,则这个成员函数 称为虚函数
当在这个类的派生类中也定义了一个完全相同的虚函数时,则称这个函数重写(覆盖)了基类的中的虚函数
如下面的这段代码:
#include <stdio.h>
#include <iostream>
#include <windows.h>
using namespace std;
class Person
{
public:
virtual void BuyTickets()
{
cout << "买票--全价" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTickets()
{
cout << "买票--半价" << endl;
}
};
void fun(Person &p)
{
p.BuyTickets();
}
int main()
{
Person p;
Student s;
fun(p);
fun(s);
system("pause");
return 0;
}
我们看到fun()函数中参数为基类的引用,按照我们之前的理解,当子类调用fun()时,这里也应该输出的是两个全价
,子类中对fun()函数进行了重写,并且这里fun()函数的参数为父类对象的引用,所以
这里构成了多态。
多态:可以理解为同一个东西有多种状态,满足不同的需求;
解释上面的现象:
当使用指针或者引用调用重写的虚函数时,这里无关类型,而是与对象有关
当你引用子类对象或者指向子类的对象时,就调用子类的虚函数,当引用父类对象或者指向父类的对象时,就调用父类的虚函数。
class Person
{
public:
virtual void BuyTickets()
{
cout << "买票--全价" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTickets()
{
cout << "买票--半价" << endl;
}
};
void fun_1(Person &p)
{
cout << "fun_1(Person &p)" << endl;
p.BuyTickets();
}
void fun_2(Person *p)
{
cout << "fun_2(Person *p)" << endl;
p->BuyTickets();
}
void fun_3(Person p)
{
cout << "fun_3(Person p)" << endl;
p.BuyTickets();
}
int main()
{
Person p;
Student s;
cout << "构成多态(引用)\n";
fun_1(p);
fun_1(s);
cout << "构成多态(指针)\n";
fun_2(&p);
fun_2(&s);
cout << "不构成多态(普通对象)\n";
fun_3(p);
fun_3(s);
system("pause");
return 0;
}
二、构成多态的条件
1.用父类的指针或者是父类的指针
2.调用的必须是虚函数(virtual修饰)
(我们可以理解为构成的多态时,调用虚函数时,就是与 类型无关,与对象有关)
上面的例子,我们在父类和子类中都用了virtual关键字,其实子类中也可以不用virtaul 修饰
class Person
{
public:
virtual void BuyTickets()
{
cout << "买票--全价" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTickets()
{
cout << "买票--半价" << endl;
}
};
void fun_1(Person &p)
{
cout << "fun_1(Person &p)" << endl;
p.BuyTickets();
}
int main()
{
Person p;
Student s;
cout << "构成多态(引用)\n";
fun_1(p);
fun_1(s);
system("pause");
return 0;
}
但是如果将父类中的virtual,去掉,而在子类中加上virtual
class Person
{
public:
void BuyTickets()
{
cout << "买票--全价" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTickets()
{
cout << "买票--半价" << endl;
}
};
void fun_1(Person &p)
{
cout << "fun_1(Person &p)" << endl;
p.BuyTickets();
}
int main()
{
Person p;
Student s;
cout << "不构成多态,父类中没有相同的虚函数\n";
fun_1(p);
fun_1(s);
system("pause");
return 0;
}
其实可以理解为当在父类中定义为虚函数时,子类继承父类时会延续其特性,也只是将这个虚函数进行了重写,本质上还是虚函数,但是我们不建议将子类中的virtual省略掉。
多学一点:
我们上面说要构成多态,两个虚函数时完全相同的,但是会有这样一种情况
当这个虚函数的返回值分别返回的时各自对象的指针,或者返回的是各自对象的引用时,也可以构成多态,这种称为
协变;
例如以下代码:
class Person
{
public:
virtual Person* BuyTickets()
{
cout << "买票--全价" << endl;
return this;
}
virtual Person &GetMoney()
{
cout << "Work" << endl;
return (*this);
}
};
class Student :public Person
{
public:
virtual Student* BuyTickets()
{
cout << "买票--半价" << endl;
return this;
}
virtual Student &GetMoney()
{
cout << "from parents" << endl;
return (*this);
}
};
void fun_1(Person &p)
{
p.BuyTickets();
}
void fun_2(Person *p)
{
p->GetMoney();
}
int main()
{
Person p;
Student s;
cout << "构成多态,协变(返回值为各自对象的指针)\n";
fun_1(p);
fun_1(s);
cout << "构成多态,协变(返回值为各自对象的引用)\n";
fun_2(&p);
fun_2(&s);
system("pause");
return 0;
}
结果:
三、构成多态时注意下列情况
1.
我们这里的虚函数的概念都是指类的成员函数并且必须为非静态的,静态成员函数是没有this指针的,是不可以通过对象来调用的,但是虚函数是放在虚表中的;
//下一篇再讲 关于虚表
2.
如果类的成员函数定义与声明分离了,我们只能在声明的时候加virtual 修饰,定义的时候不能加virtual ,
就理解为virtual 不能出现再类外面;
3.
构造函数不能定义为虚函数,因为虚函数是要放在虚表中的,而对象还没有构造出来,虚表也就是不存在的。
4.
最好不要将operator=()定义为虚函数,虽然可以将其定义为虚函数,因为我们一般来说,将一个函数定义为虚函数是为了构成多态,但是oprator=()是不能构成多态的,因为其参数类型不同,如果不能构成多态,那就最好不要定义为虚函数,容易引起混淆。
4.
最好不要在构造函数和析构函数中调用虚函数(在构成多态时),这里会发生未定义行为(C++标准没有定义的行为)我们假设一种情况,我们在父类的构造函数中使用子类对象的指针或者引用来调用虚函数,调到子类的虚函数,但是这里的父类对象可能还没有定义出来。
5.
最好
将
基类的析构函数声明为虚函数(在构成多态时),
因为有下面的场景:
class Parent
{
public:
~Parent()
{
cout << "~Parent()" << endl;
}
};
class Child :public Parent
{
public:
Child(const int a):_str(new char[a])
{}
~Child()
{
delete _str;
cout << "~Child()" << endl;
}
private:
char *_str;
};
int main()
{
Parent *p = new Child(5);
delete p;//这里析构就只析构了父类
system("pause");
return 0;
}
如果有上面的行为,我们看到,当delete p时,只是调用了父类 的析构函数(因为不构成多态时只看类型),那么子类中的_str就没有被释放,会造成内存泄露。
这里就是要解释为什么编译器在处理的时候将子类和父类的析构函数处理为一个同名函数,都是调用一个名为destructor的函数,同名函数才有机会构成多态,这样也就为了避免上面的问题。
把析构函数声明为虚函数时,这里的delete p,就会去调用子类的析构函数(因为构成多态时就看对象),就会很好的避免上面的问题。
class Parent
{
public:
virtual ~Parent()
{
cout << "~Parent()" << endl;
}
};
class Child :public Parent
{
public:
Child(const int a):_str(new char[a])
{}
virtual ~Child()
{
delete _str;
cout << "~Child()" << endl;
}
private:
char *_str;
};
int main()
{
Parent *p = new Child(5);
delete p;
system("pause");
return 0;
}
四、小总结
(1)经过学习C++中的 函数重载、重定义(隐藏)、重写(覆盖)总结一下三者的区别
(2)纯虚函数:
在虚函数的形参后面写上=0,则成员函数为纯虚函数
(3)抽象类包含纯虚函数的的类叫做抽象类(也叫做接口类),抽象类是不能实例化出对象的,纯虚函数在派生类中重新定义以后,派生类才能实例化处对象。
看下面的代码:
class Person
{
public:
virtual void show() = 0;
private:
string _name;
};
class Student:public Person
{
public:
string _stunum;
};
int main()
{
Person P;
//Student S;
system("pause");
return 0;
}
当我们想构造一个Preson对象时:
同样,按照上面的代码,当你想构造一个Student对象时:
只有在派生类中对纯虚函数进行了重写,这时候派生类才能实例化出对象
(3)友元关系不能继承:
class Person
{
friend void show(Person &P,Student &S);
protected:
string _name;
};
class Student:public Person
{
protected:
string _stunum;
};
void show(Person &P,Student &S)
{
cout<<P._name << endl;
cout << S._stunum << endl;
}
int main()
{
Person P;
Student S;
show(P,S);
system("pause");
return 0;
}
上面的代码编译不通过:
也就是说,基类中的友元函数不可以访问派生类中的私有后者保护成员。
(4)静态成员只有一份
若基类中定义了一个静态成员,则整个继承体系中只有一个这样的成员,不管定义了多少个子类对象,只能实例化一个这样的成员。
看下面的代码:
class Person
{
public:
Person()
{
++_count;
}
static int _count;//计算人数,在这里限定为共有的是为了下面测试
protected:
string _name;
};
int Person ::_count = 0;//对静态数据成员进行初始化
class Student:public Person
{
protected:
string _stunum;
};
int main()
{
Person P;
Student S;
cout << Student::_count << endl;
S._count = 0;
cout << Person::_count << endl;
system("pause");
return 0;
}
本篇完。下一篇,探索对象模型,理解多态的原理