目录
多态的意义
多态是设计模式和编写框架的一个基础,都知道的面向对象的三大概念,封装,继承和多态,其实,与其说是三大概念,不如说是三种境界,比如,封装的意义在于,突破了C语言中函数的概念,当把一个类做函数参数的时候,我们把一个类的对象传到一个被调用函数里,这样在被调用函数里面,可以直接使用这个类的属性和类的方法;再看继承,它的意义在于,继承可以复用以前人写的代码,这样就可以解决代码复用的一些难题了;最后,看多态,多态应该比继承更牛,这是因为,多态不光是继承以前人写的代码,更重要的是,多态可以复用后来人写的代码,即一个八十年代人写的代码,可以复用九十年代人写的代码,可以扩展新的标准,从这个角度看,多态应该是一个更高层次上的思想。
多态的问题抛出
赋值兼容性原则遇上函数重写,即如果子类定义了与父类中原型相同的函数会发生什么?
先定义一个父类函数:
class Parent
{
public:
Parent(int a)
{
this->a = a;
cout<<"Parent a"<<a<<endl;
}
protected:
private:
int a ;
};
再定义一个子类:
class Child : public Parent
{
public:
Child(int b) : Parent(10)
{
this->b = b;
cout<<"Child b"<<b<<endl;
}
protected:
private:
int b;
};
这样构造关系就有了,再这个场景之下,我们再写一个print函数,再父类和子类中,分别添加print函数,在父类中添加函数:
void print() //子类的和父类的函数名字一样
{
cout<<"Parent 打印 a:"<<a<<endl;
}
在子类中添加函数:
void print() //virtual 父类写了virtual,子类可写 可不写
{
cout<<"Child 打印 b:"<<b<<endl;
}
我们知道根据赋值兼容性原则,可以将子类的对象,赋给基类的指针,那么我们就直接定义一个基类的指针,
void main()
{
Parent *base = NULL;
Parent p1(20);
Child c1(30);
base = &p1;
base->print(); //执行父类的打印函数
base = &c1;
base->print(); //执行谁的函数 ? //面向对象新需求
cout<<"hello..."<<endl;
system("pause");
return ;
}
那么,当"base = &c1;"的时候,执行的print函数是谁的?先看看代码运行结果:
发现,最终执行的是父类的print函数,而不是子类的。这里就存在一个新的需求,如何来调用子类的print函数,总不能每次都去调用父类的吧!
再接着看,在主调函数中添加,新的代码段,采用引用的方式再试:
{
Parent &base2 = p1;
base2.print();
Parent &base3 = c1; //base3是c1 的别名
base3.print();
}
在这种场景下,base3就是c1,那再进行打印,看看调用的是谁的print函数:
发现调用的print函数仍旧是父类的,竟然连别名,都不别名了!
还是不服气,再试,用指针做函数参数的方式再来调用,添加一个新的函数:
void howToPrint(Parent *base)
{
base->print(); //一种调用语句 有多种表现形态...
}
void howToPrint2(Parent &base)
{
base.print();
}
分别对这两次函数,做一次函数调用:
//函数调用
howToPrint(&p1);
howToPrint(&c1);
howToPrint2(p1);
howToPrint2(c1);
发现结果和之前还是一样,还是一直调用的是父类的print函数,就每调用过子类的:
其实,我们实现的功能很简单,传来一个子类的指针对象,就执行子类的print函数,传来一个父类的指针对象,就执行父类的print函数,但经过以上尝试,发现都不行,而这就是面向对象的新需求!
下面进行理论总结:
编译器的做法不是我们期望的:
根据实际的对象类型来判断重写函数的调用,如果父类指针指向的是父类对象则调用父类中定义的函数,如果父类指针指向的是子类对象则调用子类中定义的重写函数。
我们期望的:
对同样的调用语句有多种的不同的表现形式,像这种表现形式,我们就称之为多态!
实现多态的解决方案
解决方案很简单,就是加"virtual"关键字,强调的地方有两点:1)这才是"virtual"关键字,真正的主流应用场景!之前在虚继承中的应用并不是;2)如果在父类的函数中加上了"virtual"关键字,在子类中的相同函数加不加这个关键字(一般情况下都写上,表示醒目),都无所谓,都能达到实现多态的功能。
将之前的代码进行改进,在父类的print函数前加"virtual"关键字:
virtual void print() //子类的和父类的函数名字一样
{
cout<<"Parent 打印 a:"<<a<<endl;
}
在子类的print函数前加"virtual"关键字:
virtual void print() //virtual 父类写了virtual,子类可写 可不写
{
cout<<"Child 打印 b:"<<b<<endl;
}
这个时候,在进行调用就可以了!
void main()
{
Parent *base = NULL;
Parent p1(20);
Child c1(30);
base = &p1;
base->print(); //执行父类的打印函数
base = &c1;
base->print(); //执行谁的函数 ? //面向对象新需求
cout<<"hello..."<<endl;
system("pause");
return ;
}
这样都是通过"base->print()"来调用的,就产生了不太的效果:
这样的话,运行之前的存放在主调中的三种尝试代码,都实现了想要的功能:
这样的就做到了一个调用语句有多种表现形态!这就是C++给我们提供的多态。
总体代码
dm13_类型兼容性原则遇上函数重写.cpp
#include <iostream>
using namespace std;
class Parent
{
public:
Parent(int a)
{
this->a = a;
cout<<"Parent a"<<a<<endl;
}
virtual void print() //子类的和父类的函数名字一样
{
cout<<"Parent 打印 a:"<<a<<endl;
}
protected:
private:
int a ;
};
class Child : public Parent
{
public:
Child(int b) : Parent(10)
{
this->b = b;
cout<<"Child b"<<b<<endl;
}
virtual void print() //virtual 父类写了virtual,子类可写 可不写
{
cout<<"Child 打印 b:"<<b<<endl;
}
protected:
private:
int b;
};
void howToPrint(Parent *base)
{
base->print(); //一种调用语句 有多种表现形态...
}
void howToPrint2(Parent &base)
{
base.print();
}
void main()
{
Parent *base = NULL;
Parent p1(20);
Child c1(30);
base = &p1;
base->print(); //执行父类的打印函数
base = &c1;
base->print(); //执行谁的函数 ? //面向对象新需求
{
Parent &base2 = p1;
base2.print();
Parent &base3 = c1; //base3是c1 的别名
base3.print();
}
//函数调用
howToPrint(&p1);
howToPrint(&c1);
howToPrint2(p1);
howToPrint2(c1);
cout<<"hello..."<<endl;
system("pause");
return ;
}