C++–多态
参考书籍:
- 《C++实践之路》–【美】Bartosz Milewski 著 周良忠 译
- 《C++语言程序设计教程(第二版)》–沈显君 杨进才 张勇 编著
可能看不了图片,我用的atom写的,传图片伤时间,可以到github上面看有图片的,点击这个就行了
多态这个在面向对象编程里面是非常重要的。在《C++实践之路》一书中对于多态有个很形象的比喻:
收音机、磁带机及CD机形态各不相同,但他们具有相同的音频输出接口。你可以将耳机插入此接口来收听音乐,而与音乐是否来自于调制载波、录音带上的磁畴或可塑磁盘铝层上的一系列起凹点无关。从音乐爱好者角度来看,所有这些设备都代表音频源。只要他们提供了相同的接口(音频插口),聆听音乐的过程就与之无关。
到底多态有什么优点,有什么魅力,我只能说你代码量上到一定的程度就会深深爱上他,这个多态我个人觉得是非常有趣好玩的!接下来具体来说明一下:
先来梳理一遍相关知识点:
派生类的构造与析构
在《C++语言程序设计教程》一书中是这样定义的:
在派生类对象的成员中,从基类继承来的成员被封装为基类子对象,他们的初始化由派生类构造函数隐含调用基类构造函数进行初始化;内嵌成员对象则隐含调用成员类的构造函数予以初始化;派生类新增的数据成员由派生类在自己定义的构造函数中进行初始化。
派生类的析构函数只负责把派生类新增的非对象成员清理工作做好就够了,系统会自己调用基类及成员函数的析构函数来对基类子对象及对象成员进行清理。
不得不说教科书书上讲的真是专业啊,我是讲不出这种水平的,所以用我自己的话来说一下关键点:
我们知道继承呢就是拥有了父亲那里的一切,包括父亲类的构造和析构,还可以有自己的特色(自己的新方法和新成员)。所以在初始化一个派生类对象时,从父类继承来的构造函数会隐含的调用来初始化从父类中继承来的成员,而派生类自己新增的成员只能通过自己的构造函数进行初始化。水平有限,可能解释的不太清楚,看下例子:
#include <iostream>
class Father
{
public:
Father() { std::cout << "create father" << std::endl; };
~Father() { std::cout << "delete father" << std::endl; };
};
class Son :
public Father
{
public:
Son() { std::cout << "create son" << std::endl; };
~Son() { std::cout << "delete son" << std::endl; };
};
int main()
{
Son* son = new Son();
delete son;
system("pause");
return 0;
}
运行结果:
从上面的运行结果可以看到当我们初始化一个派生类对象 son 时,程序先调用了基类 Father 的构造函数,然后再是派生类 Son 自己的构造函数。而销毁派生类对象 son 时,先调用派生类自己的析构函数,然后调用基类的析构函数,刚好与构造函数的调用顺序相反。
多态性概述
在《C++语言程序设计教程》一书中是这样说的:
多态是指同样的消息被不同类型的对象接受时导致完全不同的行为
这个干讲我讲不清(其实我现在刚开始懂点皮毛),在后面的例子中慢慢领会
虚函数
我们知道父类的对象可以指向子类对象(这是一个very nice的功能,我个人感觉这就是面向对象编程的魅力,更符合人的思维),我们看看下面一段代码:
#include <iostream>
class Man
{
public:
Man() {}
~Man() {}
void welcome() { std::cout << "Welcome man" << std::endl; }
};
class Boy:
public Man
{
public:
Boy() {}
~Boy() {}
void welcome() { std::cout << "Welcome boy" << std::endl; }
};
int main()
{
Man* man = new Man;
man->welcome();
Boy boy = Boy();
boy.welcome();
man = &boy;
man->welcome();
system("pause");
return 0;
}
运行结果如下:
我们可以看到第三个输出结果是有问题的,完全与我们想象中的不一样啊:man 指针既然指向了 boy,那调用 welcome 方法时应该显示 Welcome boy,但他显示是不是。为了让结果显示符合我们人的思维,这时候虚函数就登场了,让我们看看它是如何发挥做用的:
现在我们把 welcome 这个方法定义为虚函数,即在函数前面加关键字 virtual,如下面的代码:
#include <iostream>
class Man
{
public:
Man() {}
~Man() {}
//加上关键字virtual
virtual void welcome() { std::cout << "Welcome man" << std::endl; }
};
class Boy :
public Man
{
public:
Boy() {}
~Boy() {}
//加上关键字virtual
virtual void welcome() { std::cout << "Welcome boy" << std::endl; }
};
int main()
{
Man* man = new Man;
man->welcome();
Boy boy = Boy();
boy.welcome();
man = &boy;
man->welcome();
system("pause");
return 0;
}
运行结果如下:
我们可以看到结果运行如我们预期的一样,它的实现原理在书中也有提及,但没看懂,先不管他。
虚函数有下面一些使用注意:
- 虚函数不能是静态成员函数(即加关键字 static 的方法),也就是关键字 virtual 和 static 不能共存;虚函数也能是友元函数。因为静态成员函数和友元函数不属于某个对象。
- 即使虚函数在类的内部定义,编译时,仍将其当做非内联的,即虚函数不能成为内联函数(内联函数:在类的内部定义实现了的,像上面的 welcome 函数一样)
- 构造函数不能是虚函数,析构函数可以是虚构函数,通常我们也将析构函数声明为虚函数。
虚函数这个是很有作用的,明白为什么用它,写的代码更符合人的逻辑,更面向对象
抽象类
在《C++语言程序设计教程》中抽象的说明是:
抽象类是一种特殊的类,是为了抽象的目的而建立的,建立抽象类,就是为了通过它多态的使用其中的成员函数,为一个类族提供统一的操作平台。抽象类处于类层次的上层,一个抽象类无法自身实例化,也就是说我们无法声明一个抽象类的对象,而只能通过继承的机制,生成抽象类的非抽象派生类,然后在实例化。
像上面这种说明读的时候大多数人都是只理解表面的中文的文字意思,至于它到底在说啥,第一次的时候我也不知道,嘿嘿。这些看看例子,自己打打代码尝试一下基本上就了解了。回到正题,我们来以人的思维来谈谈抽象类的概念:
就拿买衣服这个来说:你去买衣服,你跟老板说你买衣服,老板会说你买什么样的衣服,要衬衣、T恤、外套······?然后你说要外套,老板问你什么颜色,什么款式;最后你说啥啥就买到了一件衣服。这个例子中的衣服和衬衣、T恤、外套这些都相当于抽象类了,它们在现实生活中不存在,只是一个概念,而你买的衣服是一个实体,是存在的;还有你买的衣服继承于外套,而外套又继承于衣服。感觉啰嗦了点,将就看吧,水平有限,毕竟理科生(不过个人感觉挺形象的)。我们来看下面一段代码:
#include <iostream>
class Man
{
public:
Man() {}
virtual ~Man() = 0;//这样就这样了一个抽象类
virtual void welcome() { std::cout << "Welcome man" << std::endl; }
};
int main()
{
Man* man = new Man;//我们在这里生成一个具体的抽象类 Man 对象,但编译器报错,不能生成
system("pause");
return 0;
}
带有纯虚函数的类被称为抽象类,一个抽象类至少具有一个纯虚函数(在Java中每个函数都有纯虚函数);纯虚函数定义的形式为:
virtual 函数类型 函数名 (参数表) = 0;
你也许会问这样的抽象类有什么用?看看下面的:
#include <iostream>
class Man
{
public:
Man() {}
~Man() {};
//这次换welcome函数来成为纯虚函数
virtual void welcome() = 0;
};
class Boy :
public Man
{
public:
Boy() {}
~Boy() {}
//对 welcome 的实现
void welcome() { std::cout << "Welcome man" << std::endl; }
};
class Girl :
public Man
{
public:
Girl() {}
~Girl() {}
//对 welcome 的实现
void welcome() { std::cout << "Welcome girl" << std::endl; }
};
int main()
{
Man* man;
man = new Boy;//指向 Boy 的对象
man->welcome();
man = new Girl;//指向 Girl 的对象
man->welcome();
system("pause");
return 0;
}
结果如下:
我们可以用 Man 这个对象指针随便指向 Boy 或 Girl 的对象,这个很有用的,而且它们都实现了不同的功能。