虚函数是动态绑定的基础,必须是非静态的成员函数

1、一般虚函数

1.1 引例

程序 运行结果&解释
#include <iostream>
using namespace std; class Base1
{
public:
void display() const;//这个不是虚函数
};
class Base2 : public Base1
{
public:
void display() const;
};
class Derived : public Base2
{
public:
void display() const;
};
void Base1::display() const
{
cout << "Base1::display()" << endl;
}
void Base2::display() const
{
cout << "Base2::display()" << endl;
}
void Derived::display() const
{
cout << "Derived::display()" << endl;
}
void fun(Base1 *p)
{
p->display();
}
int main()
{
Base1 b1;
Base2 b2;
Derived d1;
fun(&b1);
fun(&b2);
fun(&d1);
return 0;
}

运行结果:

Base1::display()
Base1::display()
Base1::display()

解释:

使用对象名,绑定发生在编译过程中。

根据类型兼容性规则,派生类对象的地址会被转换为指向基类的指针,fun中的p->display()会执行基类中的display()

/*
8-4 P316
虚函数成员
*/
#include <iostream>
using namespace std; class Base1
{
public:
virtual void display() const; //这个函数是虚函数,可多态
};
class Base2 : public Base1
{
public:
void display() const;
};
class Derived : public Base2
{
public:
void display() const;
};
void Base1::display() const
{
cout << "Base1::display()" << endl;
}
void Base2::display() const
{
cout << "Base2::display()" << endl;
}
void Derived::display() const
{
cout << "Derived::display()" << endl;
}
void fun(Base1 *p)
{
p->display();
}
int main()
{
Base1 b1;
Base2 b2;
Derived d1;
fun(&b1);
fun(&b2);
fun(&d1);
return 0;
}

运行结果:

Base1::display()
Base2::display()
Derived::display()

解释:

fun()中的实参p绑定到了不同子类的指针,程序知道这一点,故执行各子类的display(),实现了多态(运行时)

1.2 一般虚函数

1.2.1 什么是一般虚函数?

  1. 首先需要在基类中将这个同名函数声明为虚函数,这样通过基类类型的指针(引用)就可以使属于不同派生类的不同对象产生不同的行为,实现运行过程的多态
  2. 虚函数声明只能出现在类定义中的函数原型声明中,而不能出现在成员函数
    实现的时候!(一般不声明为内联函数)
  3. 虚函数成员语法:virtual 函数类型 函数名(形参表);

1.2.2 运行时多态需满足三个条件:

  1. 类之间满足兼容规则
  2. 声明虚函数
  3. 成员函数来调用,或者通过指针引用访问虚函数

1.2.3 系统如何判断派生类的函数成员是否为虚函数?

  1. 该函数是否与基类的虚函数有相同的名称
  2. 该函数是否与基类的虚函数有相同的参数个数及相同的对应数类型?(包括const)
  3. 该函数是否与基类的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型返回值

1.2.4 一般虚函数的补充说明

  1. 只有虚函数是动态绑定的(如果派生类需要重写与基类函数同名的函数时,应该在基类中将相应的函数声明为虚函数)
  2. 基类中声明的非虚函数,通常代表那些不希望被派生类修改的函数,是不能实现多态的。
  3. 在重写继承来的虚函数时,如果函数有缺省值,不要重新定义不同的值(虚函数是动态绑定的,但缺省形参值是静态绑定的,缺省形参值只能来自基类的定义
  4. 只有通过基类指针或者引用调用虚函数时,才会发生动态绑定!(基类的指针可以指向派生类的对象,基类的引用可以作为派生类对象的别名,但基类的对象不能表示派生类的对象)
    Derived d;
    Base *ptr=&d; //基类的指针ptr可以指向派生类的对象
    Base &ref=d; //基类的引用ref可以作为派生类对象的别名
    Base b=d; //调用Base1的复制构造函数用d构造b,b的类型是Base而非Derived
  5. 不能声明虚构造函数,但可以声明虚析构函数(保证使用基类类型的指针就能够调用适当的析构函数针对不同的对象进行清理工作)

2、虚析构函数

2.1 引例

程序 运行结果&解释
#include <iostream>
using namespace std;
class Base
{
public:
~Base(); //不是虚函数
};
Base::~Base()
{
cout << "Base destructor" << endl;
}
class Derived : public Base
{
public:
Derived();
~Derived(); //不是虚函数
private:
int *p;
}; Derived::Derived()
{
p = new int(0);
}
Derived::~Derived()
{
cout << "Derived destructor" << endl;
delete p;
}
void fun(Base *b)
{
delete b; //静态绑定,只会调用~Base()
}
int main()
{
Base *b = new Derived();
fun(b);
return 0;
}

运行结果:

Base destructor

解释:

此时通过基类指针删除派生类对象时调用的是基类的析构函数,派生类的析构函数没有被执行,因此派生类对象中动态分配的内存空间没有被释放,造成了内存泄露。

对于长期运行的程序来说,这是非常危险的!

/*
8-5 P320
虚析构函数举例
*/
#include <iostream>
using namespace std;
class Base
{
public:
// ~Base(); //不是虚函数
virtual ~Base(); //是虚函数
};
Base::~Base()
{
cout << "Base destructor" << endl;
}
class Derived : public Base
{
public:
Derived();
// ~Derived(); //不是虚函数
virtual ~Derived(); //是虚函数,基类虚函数要加virtual,派生类的函数不加virtual也行
private:
int *p;
}; Derived::Derived()
{
p = new int(0);
}
Derived::~Derived()
{
cout << "Derived destructor" << endl;
delete p;
}
void fun(Base *b)
{
delete b; //静态绑定,只会调用~Base()
}
int main()
{
Base *b = new Derived();
fun(b);
return 0;
}

运行结果:

Derived destructor
Base destructor

解释:

将基类析构析构函数声明为虚函数,在函数fun()中会执行派生类的析构函数,派生类中动态申请的内存空间被释放了。实现了多态。

2.2 虚析构函数的说明

  1. 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
  2. 派生类中如果定义了一个函数与基类中虚函数的名字相同但是形参列表不同,这仍然是合法的,但是该函数与虚函数是相互独立的,派生类中的函数并没有覆盖掉基类的版本

2.3 override说明符

override与final都不是语言关键字(keyword),只有在特定的位置才有特别含意,其他地方仍旧可以作为一般标识符(identifier)使用。

  1. 用于声明显式函数覆盖。用于在编译期间发现未覆盖的错误
  2. 运用显式覆盖,编译器会检查派生类中声明overrid的函数,在基类中是否存在可被覆盖的虚函数,若不存在,则会报错。
  3. 作用:
    为了使派生类能覆盖基类的虚函数,但是容易弄错了参数列表,导致无法完成目标。
    想通过调试发现此类错误十分困难,故使用override(C++11),在编译时编译器就会发现此类错误

例:

class Base
{
public:
virtual void f1(int) const;
virtual void f2();
void f3();
};
class Derived1 : public Base
{
public:
void f1(int) const override; //正确,f1与基类中的f1匹配
void f2(int) override; //错误,基类中没有形如f2(int)的函数
void f3() override; //错误,f3不是虚函数
void f4() override; //错误,基类中没有名为f4的函数
};

2.4 final说明符

  1. 用来避免类被继承,或是避免基类的函数被覆盖
class Base1 final
{
};
class Derived1 : Base1// 编译错误:Base1为final,不允许被继承
{
};
class Base2
{
virtual void f() final;
};
class Derived2 : Base2
{
void f(); // 编译错误:Base2::f 为final,不允许被覆盖
};

3、纯虚函数,抽象类

3.1 纯虚函数(了解概念即可)

纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本

纯虚函数的声明格式为:

virtual 函数类型 函数名(参数表) = 0;

3.2 抽象类(不k)

带有纯虚函数的类称为抽象类

class 类名
{
virtual 函数类型 函数名(参数表) = 0;
//其他成员……
};

作用:

  1. 抽象类为抽象设计的目的而声明
  2. 将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为
  3. 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现

注意:

  1. 抽象类只能作为基类来使用
  2. 不能定义抽象类的对象
  3. 可以定义抽象类的指针或者引用(从而运用虚函数多态)

3.3 例8-6

/*
8-6 P323
抽象类举例
*/
#include <iostream>
using namespace std; class Base1 //基类Base1定义
{
public:
virtual void display() const = 0; //纯虚函数
}; class Base2 : public Base1 //公有派生类Base2定义
{
public:
void display() const; //覆盖基类纯虚函数
};
void Base2::display() const
{
cout << "Base2::display" << endl;
} class Derived : public Base2 //公有派生类Derived定义
{
public:
void display() const;
};
void Derived::display() const
{
cout << "Derived::display" << endl;
}
void fun(Base1 *ptr)
{
ptr->display();
}
int main()
{
// Base1 b1;//错误,声明基类对象
Base1 *p; //正确,声明基类指针
Base2 b2; //声明派生类对象
Derived d1; //声明派生类对象 p = &b2;
fun(p); //调用派生类Base2函数成员
p = &d1;
fun(p); //调用派生类Derived函数成员 return 0;
}

运行结果:

Base2::display
Derived::display

参考:

C++语言程序设计(第5版),郑莉,清华大学

【C++复习】第八章 多态性(2)(虚函数,纯虚函数)的更多相关文章

  1. 虚函数&纯虚函数&抽象类&虚继承

    C++ 虚函数&纯虚函数&抽象类&接口&虚基类   1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过 ...

  2. 【转】C++ 虚函数&纯虚函数&抽象类&接口&虚基类

    1. 动态多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过父类的指针调用实际子类的成员函数. 多态性就是允许将子类类型的指针赋值给父类类型 ...

  3. C++ 虚函数&纯虚函数&抽象类&接口&虚基类(转)

    http://www.cnblogs.com/fly1988happy/archive/2012/09/25/2701237.html 1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多 ...

  4. C++基础知识 基类指针、虚函数、多态性、纯虚函数、虚析构

    一.基类指针.派生类指针 父类指针可以new一个子类对象 二.虚函数 有没有一个解决方法,使我们只定义一个对象指针,就可以调用父类,以及各个子类的同名函数? 有解决方案,这个对象指针必须是一个父类类型 ...

  5. C++学习基础十二——纯虚函数与抽象类

    一.C++中纯虚函数与抽象类: 1.含有一个或多个纯虚函数的类成为抽象类,注意此处是纯虚函数,而不是虚函数. 2.如果一个子类继承抽象类,则必须实现父类中的纯虚函数,否则该类也为抽象类. 3.如果一个 ...

  6. c++虚函数,纯虚函数,抽象类,覆盖,重载,隐藏

    C++虚函数表解析(转) ——写的真不错,忍不住转了  http://blog.csdn.net/hairetz/article/details/4137000 浅谈C++多态性  http://bl ...

  7. C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类

    类继承 在C++类继承中,一个派生类可以从一个基类派生,也可以从多个基类派生. 从一个基类派生的继承称为单继承:从多个基类派生的继承称为多继承. //单继承的定义 class B:public A { ...

  8. C++ Primer--虚函数与纯虚函数的区别

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...

  9. C++:抽象基类和纯虚函数的理解

    转载地址:http://blog.csdn.net/acs713/article/details/7352440 抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层. ...

  10. c++ 多态,虚函数、重载函数、模版函数

    c++三大特性:封装.继承.多态.封装使代码模块化,继承扩展已存在的代码,多态的目的是为了接口重用 虚函数实现:虚函数表:指针放到虚函数表 多态:同名函数对应到不同的实现 构造父类指针指向子类的对象 ...

随机推荐

  1. k8s之pod的生命周期

    pod生命周期 和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体. Pod 会被创建.赋予一个唯一的 ID(UID),并被调度到节点,并在终止(根据重启策略)或删除之前 ...

  2. fabric学习笔记7

    Fabric2.0Java SDK实践-合约交易 20201303张奕博 2023.1.18 1.创建基础工程 新建一个Maven工程,添加以下依赖 <dependency> <gr ...

  3. VMware-实用网站

    二进制包的获取方式 ftp://ftp.redhat.com推荐网站  www.rpmfind.net相应的官方网站http://www.mysql.com

  4. P1002 [NOIP2002 普及组] 过河卒

    P1002 [NOIP2002 普及组] 过河卒 题目见上. 一个经典的递推题 递推不会的看下面: https://www.cnblogs.com/haoningdeboke-2022/p/16247 ...

  5. jenkins目录

    Jenkins目录详解: jobs目录:创建的所有jenkins工程.并含有所有构建历史记录和日志.其中config.xml为具体配置. plugins:所有插件 workspace:构建工程本机源码 ...

  6. monkey自动化脚本

    获取第三方安装包:手机需root,adb shell>cd data/app>ls(获取相应app信息)>cd +相应app信息>base.apk(安装包) 获取第三方安装包: ...

  7. jar包下不下来

    1.maven中的settings.xml文件中的镜像资源配置 <mirror> <id>alimaven</id> <name>aliyun mave ...

  8. win10企业版在线转换成win10专业版

    1.下载windows附件包,解压到C盘根目录 https://pan.baidu.com/s/19Zyrav9sriS9nFyJsM8ydQ 提取码:gsp6 2.运行命令 2.1.以管理员身份运行 ...

  9. 关于JWT的.net 使用(简单明了直接代码)

    首先第一步:下载插件! 在ui层---控制器所在层添加一个类 然后复制如下 using Microsoft.Extensions.Configuration; using Microsoft.Iden ...

  10. 12组-Beta冲刺-4/5

    一.基本情况 队名:字节不跳动 组长博客:https://www.cnblogs.com/147258369k/p/15604454.html Github链接:https://github.com/ ...