第十五章 多态性和虚函数
/**
* 书本:【ThinkingInC++】
* 功能:纯抽象类
* 时间:2014年10月6日13:10:28
* 作者:cutter_point
*/
#include <iostream>
using namespace std;
enum note {middleC, Csharp, Cflat};
//创建一个抽象类
//基类
class Instrument
{
public:
//纯虚函数,不可以对纯虚函数进行传值方式的调用
virtual void play(note) const=0;
virtual char* what() const=0;
virtual void adjust(int)=0;
};
//public继承Instrument
//派生类1
class Wind : public Instrument
{
public:
//这里后面的const是为了使函数无法更改私有成员
void play(note) const {cout<<"Wind::play"<<endl; }
char* what() const {return "Wind"; }
void adjust(int) {}
};
//不同的类共有继承Instrument
//派生类2
class Percussion : public Instrument
{
public:
//继承的纯虚函数必须全部实现
void play(note) const { cout<<"Percussion::play"<<endl; }
char* what() const {return "Percussion"; }
void adjust(int) {}
};
//派生类3
class Stringed : public Instrument
{
public:
void play(note) const { cout<<"Stringed::play"<<endl; }
char* what() const { return "Stringed"; }
void adjust(int) {}
};
//派生类的继续派生类1
class Brass : public Wind
{
public:
void play(note) const { cout<<"Brass::play"<<endl; }
char* what() const { return "Brass"; }
};
//派生类的继续派生类2
class Woodwind : public Wind
{
public:
void play(note) const { cout<<"Woodwind::play"<<endl; }
char* what() const { return "Woodwind"; }
};
//利用一个函数接受基类,然后使用的时候传不同的类型
void tune(Instrument& i)
{
i.play(middleC); //用的是枚举类型,如果是我写,我估计不会这样做,我是没体会到枚举类型的好处
}
//另外一个函数
void f(Instrument& i) { i.adjust(1); }
int main()
{
Wind flute; //一个派生类的对象
Percussion drum;
Stringed violin;
Brass flugelhorn;
Woodwind recorder;
//调用函数
tune(flute);
tune(drum);
tune(violin);
tune(flugelhorn);
tune(recorder);
//调用另外一个函数
f(flugelhorn) ;
return 0;
}
/**
* 书本:【ThinkingInC++】
* 功能:纯虚函数里面的公用代码
* 时间:2014年10月6日13:11:31
* 作者:cutter_point
*/
#include <iostream>
using namespace std;
class Pet
{
public:
virtual void speak() const=0;
virtual void eat() const=0;
//虚函数无法内联
//virtual void sleep() const=0 {}
};
//不能定义为内联
void Pet::eat() const
{
cout<<"Pet::eat()"<<endl;
}
void Pet::speak() const
{
cout<<"Pet::speak()"<<endl;
}
class Dog : public Pet
{
public:
void speak() const { Pet::speak(); }
void eat() const { Pet::eat(); }
};
int main()
{
Dog smile; //我是不是应该养只狗?
smile.speak();
smile.eat();
return 0;
}
抽象基类和纯虚函数
在基类中加入至少一个纯虚函数,来使基类成为抽象类。纯虚函数使用关键字virtual,并且在其后面加上=0.
继承一个抽象类时,必须实现所有的纯虚函数,否则继承出的类也将是一个抽象类。
Virtual将会降低程序的效率。
RTTI
/**
* 书本:【ThinkingInC++】
* 功能:在继承的类中在加一个虚函数
* 时间:2014年10月6日13:07:27
* 作者:cutter_point
*/
#include<iostream>
#include<string>
usingnamespace std;
class Pet
{
string pname;
public:
//构造函数,参数是const类型的引用,不能更改
Pet(const string& petName) :pname(petName) {}
virtual string name() const { return pname;}
virtual string speak() const { return""; }
};
class Dog :public Pet
{
string name;
public:
Dog(const string& petName) :Pet(petName) {}
//创建一个新的虚函数
virtual string sit() const { returnPet::name()+" sits"; }
string speak() const { returnPet::name()+" says 'Bark!'"; }
};
int main()
{
Pet* p[]={new Pet("generic"), newDog("bob") };
cout<<"p[0]->speak() ="<<p[0]->speak()<<endl;
cout<<"p[1]->speak() ="<<p[1]->speak()<<endl;
//这里是会报错的,因为p是指向Pet的指针,对象向上类型转换,安全
//这里涉及到RTTI(Run-Time Type Identification, RTTi),运行时类型辨认
// cout<<"p[1]->sit() ="<<p[1]->sit()<<endl;
return 0;
}
RTTI(Run-TimeType Identification, RTTi),运行时类型辨认
对象切片
就是想上类型转换,吧不同的地方都舍弃了,到最后得到和基类相同的属性。
重载和重新定义
重新定义一个基类中的重载函数将会隐藏所有该函数的其他基类版本
向下类型转换必须显示转换,不能隐式转换用
dynamic_cast<>
/**
* 书本:【ThinkingInC++】
* 功能:重载和重新定义
* 时间:2014年10月6日13:13:34
* 作者:cutter_point
*/
/*
重新定义一个基类中的重载函数将会隐藏所有该函数的其他基类版本
*/
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
//这里先定义了两个重载函数
virtual int f() const { cout<<"Base::f()\n"; return 1; }
virtual void f(string) const { cout<<"Base::f(string)\n"; }
//一个无关的函数
virtual void g() const { cout<<"Base::g()"<<endl; }
};
//一个派生类
class Derived1 : public Base
{
public:
//重新定义一个函数
void g() const { cout<<"Derived1::g()"<<endl; }
};
//第二个派生类
class Derived2 : public Base
{
public:
//重新定义一个虚函数,这个会隐藏基类的其他版本
int f() const { cout<<"Derived2::f()\n"; return 2; }
};
//第三个派生类
class Derived3 : public Base
{
public:
//我想改变它的返回值
//! void f() const { cout<<"Derived3::f()\n"; } //这里提示错误virtual int Base::f() const
//我们看到这两个函数的返回值是不一样的,结果出错了
//这是为了我们能够多态地通过基类调用函数,规定的
};
class Derived4 : public Base
{
public:
//我们在试一试改变它的参数
int f(int) const { cout<<"Derived4::f(int)\n"; return 4; }
};
int main()
{
cout<<">>------------------------------1-----------------------------------<<"<<endl;
string s("cutter_point");
Derived1 d1;
int x=d1.f(); //这里x应该为1
cout<<"d1 x:"<<x<<endl;
d1.f(s); //看看这里调用的是哪个地方的f()
cout<<">>------------------------------2-----------------------------------<<"<<endl;
Derived2 d2;
x=d2.f();
cout<<"d2 x:"<<x<<endl;
//! d2.f(s); //这里看看基类的重载版本是否失效了,结果果然错误
cout<<">>------------------------------4-----------------------------------<<"<<endl;
Derived4 d4;
x=d4.f(1); //修改了参数的重新定义
cout<<"d4 x:"<<x<<endl;
//修改参数后其他版本应该也会被隐藏
//!x=d4.f(); //果然报错
Base& br=d4; //向上类型转换,一个引用
//! br.f(1); //基类里面没有这个函数,带有参数的
br.f();
br.f(s); //看看这两个函数调用的是哪个版本的
return 0;
}
/**
* 书本:【ThinkingInC++】
* 功能:变量返回类型
* 时间:2014年10月6日13:14:12
* 作者:cutter_point
*/
#include <iostream>
#include <string>
using namespace std;
//一个动物食物的基类
class PetFood //这是一个抽象类
{
public:
virtual string foodType() const = 0; //纯虚函数
};
class Pet //动物基类,同上
{
public:
virtual string type() const = 0;
virtual PetFood* eats() = 0; //返回一个指向基类的指针
};
class Bird : public Pet
{
public:
string type() const { return "Bird"; }
class BirdFood : public PetFood
{
public:
string foodType() const { return "Bird food"; }
};
//向上类型转换
PetFood* eats() { return &bf; } //返回的类型是基类,return的是派生类的引用
private:
BirdFood bf;
};
class Cat : public Pet
{
public:
string type() const { return "Cat"; }
class CatFood : public PetFood
{
public:
string foodType() const { return "Birds"; }
};
//明确写出返回的类型
CatFood* eats() { return &cf; }
private:
CatFood cf;
};
int main()
{
Bird b;
Cat c;
Pet* p[]={ &b, &c };
for(int i=0 ; i < sizeof(p)/sizeof(*p) ; ++i)
{
cout<<p[i]->type()<<" eats "<<p[i]->eats()->foodType()<<endl;
}
Cat::CatFood* cf=c.eats();
Bird::BirdFood* bf;
//! bf=b.eats(); //这个无法转换,返回的是PetFood,要向下类型转换要显示的
bf=dynamic_cast<Bird::BirdFood*>(b.eats());
return 0;
}
纯虚拟析构函数
一般的纯虚函数是不会有内联的情况的,但是纯虚拟析构函数可以内联。
虚函数 ,子类可以不重写,直接继承父类 的方法来使用,也可以重写
但是纯虚函数是子类是必须重写了才可以使用
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
/**
* 书本:【ThinkingInC++】
* 功能:判定纯虚拟析构函数会被调用
* 时间:2014年10月6日13:15:33
* 作者:cutter_point
*/
#include <iostream>
using namespace std;
class Pet
{
public:
virtual ~Pet() = 0;
};
//纯虚函数必须得有函数体
Pet::~Pet()
{
cout<<"~Pet()"<<endl;
}
class Dog : public Pet
{
public:
~Dog(){ cout<<"~Dog()"<<endl; }
};
int main()
{
Pet* p=new Dog; //向上类型转换
delete p;
return 0;
}
先调用Dog里面的析构函数,然后调用纯虚拟析构函数。
都是先调用派生类的析构函数,然后调用基类的析构函数
和构造函数相反
/**
* 书本:【ThinkingInC++】
* 功能:关于单根继承,实现stack
* 时间:2014年10月6日13:16:50
* 作者:cutter_point
*/
#ifndef OSTACK_H_INCLUDED
#define OSTACK_H_INCLUDED
class Object
{
public:
virtual ~Object() = 0;
};
//纯虚拟析构函数可以有内联情况,一般的纯虚函数不能内联
inline Object::~Object() {}
class Stack
{
struct Link
{
Object* data;
Link* next;
Link(Object* dat, Link* nxt) : data(dat), next(nxt) {}
}*head;
public:
Stack() : head(0) {}
~Stack()
{
while(head) //只要头不为空
{
delete pop(); //吧数据弹出栈并回收
}
}
void push(Object* dat) //加入数据
{
head=new Link(dat, head); //吧新的节点插入到最前面,成为新的head,栈底就是那个head(0)
}
//弹出数据但是不在栈中删除
Object* peek() const
{
return head ? head->data : 0;
}
Object* pop()
{
//如果栈为空就直接返回0
if(head == 0) return 0;
//如果不为空,准备好返回的数据
Object* result=head->data;
//吧栈顶的节点准备断开
Link* oldHead=head;
//head更新为新的栈顶
head=head->next;
//回收原来的老栈顶
delete oldHead;
//返回老的栈顶原始
return result;
}
};
#endif // OSTACK_H_INCLUDED
/**
* 书本:【ThinkingInC++】
* 功能:关于单根继承,实现stack的测试文件
* 时间:2014年10月6日13:17:21
* 作者:cutter_point
*/
#include "OStack.h"
#include "../require.h" //前面的文件给出了它的定义
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
class MyString : public string, public Object
{
public:
~MyString() { cout<<"deleting string: "<<*this<<endl; }
MyString(string s) : string(s) {}
};
int main()
{
ifstream in("OStackTest.cpp");
assure(in, "OStackTest.cpp"); //这可以去掉
Stack textlines; //一个Stack
//输入流
string line;
while(getline(in, line))
{
textlines.push(new MyString(line)); //都是继承过Objec的对象,可以向上类型转换
}
//输出到窗口
MyString* s;
for(int i=0 ; i < 100 ; ++i) //10行,只打印了10行,剩余的会自动清除掉,不用这里delete
{
if((s=(MyString*)textlines.pop()) == 0) break; //pop里面已经回收了空间
cout<<*s<<endl;
//delete s; //这个只是为了回收MyString创建的空间
}
delete s; //这样也是OK的
cout<<"析构函数的调用-------------------------------------"<<endl;
return 0;
}