【ThinkingInC++】67、多态性和虚函数

第十五章 多态性和虚函数

/**
* 书本:【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;
}
时间: 2024-10-06 20:26:17

【ThinkingInC++】67、多态性和虚函数的相关文章

sdut 6-1 多态性与虚函数

6-1 多态性与虚函数 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描述 通过本题目的练习可以掌握多态性的概念和虚函数的定义和使用方法 要求定义一个基类Pet,它有一个成员函数Speak()用于输出pet的叫声.;派生类Dog和Cat从基类Pet派生而来.他们从基类继承并重新改写了speak()函数,分别用于输出Dog类和Cat类的叫声.要求利用虚函数编写代码,使得程序能够输出下面的内容. 输入 无 输出 输出数据共有3行,本题

sdut 6-2 多态性与虚函数

6-2 多态性与虚函数 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描述 通过本题目的练习可以掌握多态性的概念和虚函数的定义和使用方法 要求定义一个基类Pet,它有一个字符指针型数据成员name和一个虚成员函数Speak()用于输出pet的叫声.;派生类Dog和Cat从基类Pet派生而来.他们从基类继承并重新改写了speak()函数,分别用于输出Dog类和Cat类的叫声(具体输出内容参考示例输出).要求利用虚函数技术编写代码,使

VC++ 之 多态性与虚函数

多态性是面向对象程序设计的关键技术之一.利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能.若程序设计语言不支持多态性,不能称为面向对象的语言. 在C++中有两种多态性: 编译时的多态性:通过函数的重载和运算符的重载来实现的. 运行时的多态性:在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据具体情况来动态地确定.它是通过类继承关系和虚函数来实现的,目的也是建立一种通用的程序. 虚函数的定义 ◆ 1.定义格式虚函数是一个类的成员函数,定义格式如下: 

c++特别要点:多态性与虚函数

本来是准备在工厂模式中顺便整理.但粗略浏览了,内容还是很多,需要单独开一篇. 一.什么是多态性? 多态性可以概括为“一个接口,多种方法”. 多态与非多态的区别在于“成员函数调用地址的早绑定和晚绑定”.“早绑定”在编译期就可以确定函数的调用地址,是静态的:“晚绑定”在运行时才能确定函数的调用地址,是动态的. 多态的作用是什么呢?在面向对象的编程中,“封装”使得代码模块化:“继承”可以扩展以存在的代码:“多态”使得接口重用. 二.多态性的c++实现:虚函数 声明基类的指针,该指针指向子类的对象.调用

C++之多态性与虚函数

面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了“一个接口,多种方法”. 从实现的角度来讲,多态可以分为两类:编译时的多态性和运行时的多态性.前者是通过静态联编来实现的,比如C++中通过函数的重载和运算符的重载.后者则是通过动态联编来实现的,在C++中运行时的多态性主要是通过虚函数来实现的,也正是今天我们要讲的主要内容. 1.不过在说虚函数之前,我想先介

[转]C++之多态性与虚函数

面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了“一个接口,多种方法”. 从实现的角度来讲,多态可以分为两类:编译时的多态性和运行时的多态性.前者是通过静态联编来实现的,比如C++中通过函数的重载和运算符的重载.后者则是通过动态联编来实现的,在C++中运行时的多态性主要是通过虚函数来实现的,也正是今天我们要讲的主要内容. 1.不过在说虚函数之前,我想先介

多态性与虚函数

多态性 多态性是面向对象程序设计的一个重要特征.如果一种语言只支持类而不支持多态,是不能被称为面向对象语言的.只能说是基于对象的,如Ada,VB就属于此类. 在C++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数. 在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去相应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的

C++多态性与虚函数

面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了"一个接口,多种方法". 从实现的角度来讲,多态可以分为两类:编译时的多态性和运行时的多态性.前者是通过静态联编来实现的,比如C++中通过函数的重载和运算符的重载.后者则是通过动态联编来实现的,在C++中运行时的多态性主要是通过虚函数来实现的. 赋值兼容     不过在说虚函数之前,先介绍一个有关

第十三周阅读项目(4):多态性与虚函数

(1)代码: #include <iostream> using namespace std; class Vehicle //交通工具 { public: void run() const { cout << "run a vehicle. "<<endl; } }; class Car: public Vehicle //汽车 { public: void run() const { cout << "run a car.