C++三大特性:封装性、继承性、多态性
1多态性的概念
简单概括:一个接口,多种方法。
意义:程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念
2关于多态的一些阐述
C++的多态性只有通过虚函数才能体现出来,虚函数允许子类重新定义父类。
3为什么虚函数就体现了C++的多态性呢?
没有加virtual修饰的函数是不是能体现多态性呢?看个例子
这样不太能看出问题来,再加俩指针
这样问题就出来了
我想要基类和派生类各自调用各自的print()函数,可为什么第一个是的,第二个却不是的了呢?
(1)
这是理解多态性的一个关键点:
首先,
class Base{
public:
void print(){ cout << “This is Base .” << endl; }
};
class Derive:public Base{
public:
void print(){ cout << “This is Derive .” << endl; }
};
…
Base A;
Derive B;
先埋个伏笔。看清楚,现在这个时候的基类里的print()不是一个虚函数。
那么我们正常理解。
用Base 和 Derive分别实例化两个对象A 、B,且Base继承于Derive。
对象A正常调用它自己的print();对象B的print()和对象A的print()重名了,它不知道该调用谁了。
这时候,因为“现在这个时候的基类里的print()不是一个虚函数。”所以,派生类会发生名字遮蔽。意思就是说,对象B的自己print()函数遮盖住继承过来的print(),不再使用继承的而是使用自己的。
所以,这里会按照程序员的想法正常显示(让他们各自显示各自的)。
(2)
那为什么下面那个又不各自显示各自的了呢?
这又是理解多态性的一个关键点:
class Base{
public:
void print(){ cout << “This is Base .” << endl; }
};
class Derive:public Base{
public:
void print(){ cout << “This is Derive .” << endl; }
};
…
Base A;
Derive B;
Base* p = &A;
Base* q = &B;
这里就涉及到一个很重要的概念:向上转型
什么是向上转型?其前提条件是什么?有哪些类型可以实现向上转型?
定义:类是一种数据类型,可以发生数据类型转换。将派生类赋值给基类,这在 C++ 中称为向上转型(Upcasting)。相应地,将基类赋值给派生类称为向下转型(Downcasting)。向上转型非常安全,可以由编译器自动完成;向下转型有风险,需要程序员手动干预。
前提:这种转换只有在基类和派生类之间才有意义,并且只能将派生类赋值给基类。
种类:将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用
Base* p = &A;
Base* q = &B;
这个属于“将派生类指针赋值给基类指针”,是向上转型的一种。
但是向上转型会有后遗症的,这里简要概括下来就是,派生类如果大于基类,派生类里的一些东西就会舍去,而保留和基类大致相当的一些东西。因为,派生类本身就是在继承基类的基础上加一些自己的新的东西,你非得“大材小用”非得向基类靠拢,那就得剪裁那些新的东西,只保留和基类一致的东西。下面,上图理解:
所以,下面的这些就可以解释了。
Base* p = &A;
Base* q = &B;
Base* p = &A;//这句话,正常理解,就是定义一个指针,是Base*类型的,指向对象A;
Base* q = &B;//这句话,就又说法了。这是一个典型的向上转型 : “将派生类指针赋值给基类指针”。那么它就会有后遗症,“大材小用”,派生类自己新增的一些东西舍去了,只保留了和基类一致的一些东西。那么,你派生类和基类又有何区别?所以,会有两个 This is Base . 输出。
到此,先稍微梳理一下。
我们之前是想让对象A B各自调用各自的print(),有两种方法:
Base A;
Derive B;
Base* p = &A;
Base* q = &B;
//方法一
A.print();
B.print();
cout << “\n” << endl;
//方法二
p->print();
q->print();
方法一可用,但是有缺点。
方法二不可用,因为派生类总被限制在基类里,无法调用子类自己的被重写过的函数。
好,在这里总结一下:
我们之前是想让对象A B各自调用各自的print(),有两种方法。
但是,这两种方法不尽人意。那么有没有一种方法呢?
以上全是铺垫,下面,正式介绍虚函数与多态。
其实多态说来也简单,就是“一个接口,多种方法” , “对于多态来说,实际上就是用父类型的指针指向子类的实例,然后通过父类的指针调用子类的成员函数”
注意,基类里我多加的virtual关键字!什么是虚函数,加了virtual关键字,这个函数就是虚函数了。
class Base{
public:
virtual void print(){ cout << “This is Base .” << endl; }
};
运行结果:
按照我们的要求各自显示各自的了。这是为什么?
因为概念变了,变成了“虚函数与多态”。
通过虚函数,我们可以达到这样的目的:
Base* p = &A;//基类指针指向基类对象
p->print();//编译器会调用基类方法
Base* q = &B;//基类指针指向派生类对象
p->print();/* 如果没有virtual定义的虚函数,编译器为了安全会调用基类方法,就像我们之前说的“方法二不可用,因为派生类总被限制在基类里,无法调用子类自己的被重写过的函数。” 。但是,一旦我们定义了虚函数,Base* q = &B; p->print();编译器就会调用 派生类自己的print()。 */
现在,梳理一下:
如果想基类调用基类方法,派生类自己调用派生类方法,该怎么实现呢?
引入多态概念,使用虚函数实现。
Base* p = &A;//基类指针指向基类对象
p->print();//指向基类的指针p会实现“基类调用基类方法”
Base* q = &B;//基类指针指向派生类对象
q->print();//指向派生类的指针q会实现“派生类自己调用派生类方法”
如果你还想使用其他派生类仍然可以继续使用指针q实现,只要让q是其他类的指针类型,然后指向你想指向的其他派生类对象即可。
4总结,C++三大特性:封装性、继承性、多态性。多态和虚函数相互联系。
5给下面的任务开个头:那几句常挂嘴边的多态的定义到底该怎么理解?虚函数到底是怎么一回事?