更多知识点:C++知识点目录索引
接上一篇的c++知识点–继承
什么是多态?
百度百科:
多态(Polymorphism)按字面的意思就是”多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述–多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自”Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在ObjectPascal和C++中都是通过虚函数实现的。
以上是百度百科的解释,下面给出自己的解释:
多态就是一个对象的多种形态,例如:我们只提供一个卖票窗口,在这个窗口可以买到成人票和学生票,将这个卖票的窗口看作一个对象,这个对象指向成人,那就买成人票,指向学生,就买学生票
C++中形成多态的条件
成员函数必须是虚函数
虚函数必须重写
使用对象的指针或引用调用重写的虚函数
对象调函数
不构成多态:
对象调函数与对象的类型有关,是哪个类型就调用哪个类型的函数
构成多态
跟指向的对象有关,指向哪个对象就调用哪个对象的函数
例子:
第一种情况:不构成多态
class Person
{
public:
void BuyTickets()
{
cout << "买票---全价" << endl;
}
protected:
string _name;
};
class Student:public Person
{
public:
void BuyTickets()
{
cout << "买票----半价" << endl;
}
protected:
int _num;
};
void Fun(Person& p)
{
p.BuyTickets();
}
int main()
{
Person p;
Student s;
Fun(p);
Fun(s);
system("pause");
return 0;
}
第二种情况:构成多态
class Person
{
public:
virtual void BuyTickets()
{
cout << "买票---全价" << endl;
}
protected:
string _name;
};
class Student:public Person
{
public:
virtual void BuyTickets()
{
cout << "买票----半价" << endl;
}
protected:
int _num;
};
void Fun(Person& p)
{
p.BuyTickets();
}
int main()
{
Person p;
Student s;
Fun(p);
Fun(s);
system("pause");
return 0;
}
总结:
派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外;协变是指基类和父类中含有函数名相同,参数列表相同,返回值类型不同的成员函数)
基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
只有类的成员函数才能定义为虚函数。
静态成员函数不能定义为虚函数。
如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual,这里是为了与第三条原则保持一致
构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但是最好不要将operator=定义为虚函数,因为容易使用时容易引起混淆。
不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。
最好把基类的析构函数声明为虚函数。
对于最后一点,在这里说明一下:
先看下面的代码:
class AA
{
public:
AA()
{
cout << "AA()" << endl;
}
~AA()
{
cout << "~AA()" << endl;
}
};
class BB :public AA
{
public:
BB()
{
cout << "BB()" << endl;
}
~BB()
{
cout << "~BB()" << endl;
}
};
int main()
{
AA* p = new BB;
delete p;
system("pause");
return 0;
}
结果:
分析: 上面这段代码,BB是动态开辟出来的,所以要手动释放;此时对象BB是p指针指向的,并且指针p是对象AA的,因此在释放的时候,上图中只调用了AA的析构,对象BB并没有被释放,在这里就造成了内存泄漏
解决上述问题,可将父类的析构函数定义成虚函数,这就体现了最后一条总结的作用:
原因:
子类继承的父类,必然继承了父类的虚函数特性
子类既然继承了父类的虚函数特性,那么子类的虚函数和父类的虚函数构成了重写
此时满足多态的特性,虚函数重写;我们知道delete在底层实际上调用的是free,此时调用者是父类AA的指针,满足多态的第二个条件,使用对象的指针,因此是多态
满足多态的特性,那么对象调函数时,和调用的对象有关,此时既析构了父类对象AA,又析构了子类对象BB
抽象类(接口类)
在介绍抽象类之前先来看下纯虚函数:
纯虚函数是指在虚函数的后面写上 = 0;则成员函数为纯虚函数。
例如:
class AA
{
public:
virtual void Show() = 0;//纯虚函数
};
class BB : public AA
{};
上面实现了纯虚函数,那么抽象类指的就是类中的成员函数包含了纯虚函数,该类被称为抽象类。
抽象类的特点:
抽象类只能用作其他类的基类,不能建立抽象类对象。
抽象类不能用作参数类型、函数返回类型或显式转换的类型
可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。
抽象类的纯虚函数只声明不实现
例子:
class AA
{
public:
virtual void Show() = 0;
protected:
string _name;
};
class BB : public AA
{
public:
void Show(int a)//不构成重写
{
cout << "Show()" << endl;
}
};
int main()
{
AA aa;
BB bb;//不构成重写,BB继承了AA的特性,不能实例化出对象
}
class AA
{
public:
virtual void Show() = 0;
protected:
string _name;
};
class BB : public AA
{
public:
void Show()//BB的成员函数重写了AA的纯虚函数
{
cout << "Show()" << endl;
}
};
int main()
{
AA aa;
BB bb;//可以实例化出对象
}
友元与继承
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
例:
class AA
{
friend void Show()
{
cout << "Show()" << endl;
}
protected:
string _name;
};
class BB:public AA
{
protected:
int _num;
};
void Show(AA& aa, BB& bb)
{
cout << aa._name << endl;
cout << bb._name << endl;
cout << bb._num << endl;
}
void Test()
{
AA aa;
BB bb;
Show(aa, bb);
}