C++语言基础(12)-虚函数

时间:2021-10-12 20:46:52

一.虚函数使用的注意事项

1.只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。

2.为了方便,你可以只将基类中的函数声明为虚函数,这样所有子类中具有遮蔽(覆盖)关系的同名函数都将自动成为虚函数。

3. 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。

4.只有子类的虚函数遮蔽基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问子类函数)。例如基类虚函数的原型为virtual void func();,派生类虚函数的原型为virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句p -> func(100);将会出错,而语句p -> func();将调用基类的函数。

5.构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义。.

6.析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。

二.具体用法

#include <iostream>
using namespace std; //基类Base
class Base{
public:
virtual void func();
virtual void func(int);
};
void Base::func(){
cout<<"void Base::func()"<<endl;
}
void Base::func(int n){
cout<<"void Base::func(int)"<<endl;
} //派生类Derived
class Derived: public Base{
public:
void func();
void func(char *);
};
void Derived::func(){
cout<<"void Derived::func()"<<endl;
}
void Derived::func(char *str){
cout<<"void Derived::func(char *)"<<endl;
} int main(){
Base *p = new Derived();
p -> func(); //输出void Derived::func()
p -> func(); //输出void Base::func(int)
p -> func("http://c.biancheng.net"); //compile error return ;
}

注意:p -> func("http://c.biancheng.net");会出现编译错误,因为子类Derived中的 void func(char *)并未对父类中的virtual void func(int)造成覆盖,两个函数的函数原型不同,所以无法构造多态,自然也不能通过基类的指针来访问子类函数。

三.将基类的析构函数定义成虚函数的必要性

先看下面的例子:

#include <iostream>
using namespace std; //基类
class Base{
public:
Base();
~Base();
protected:
char *str;
};
Base::Base(){
str = new char[];
cout<<"Base constructor"<<endl;
}
Base::~Base(){
delete[] str;
cout<<"Base destructor"<<endl;
} //派生类
class Derived: public Base{
public:
Derived();
~Derived();
private:
char *name;
};
Derived::Derived(){
name = new char[];
cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
delete[] name;
cout<<"Derived destructor"<<endl;
} int main(){
Base *pb = new Derived();
delete pb; cout<<"-------------------"<<endl; Derived *pd = new Derived();
delete pd; return ;
}

运行结果:
Base constructor
Derived constructor
Base destructor
-------------------
Base constructor
Derived constructor
Derived destructor
Base destructor

在本例中,不调用派生类的析构函数会导致 name 指向的 100 个 char 类型的内存空间得不到释放;除非程序运行结束由操作系统回收,否则就再也没有机会释放这些内存。这是典型的内存泄露

注意:

delete pb; 不调用子类的析构函数是因为:这里的析构函数是非虚函数,通过指针访问非虚函数时,编译器会根据指针类型来确定要调用的函数;也就是说,指针指向哪个类
就调用哪个类的函数。pb是基类的指针,所以不管它指向的是基类的对象还是子类的对象,始终都是调用基类的析构函数

delete pd 会同时调用子类和基类的析构函数是因为:pd是子类的指针,编译器会根据它的类型匹配到子类的析构函数,在执行子类的析构函数的过程中,又会调用基类的析构函数
子类的析构函数始终会调用基类的析构函数,且这个过程是隐式完成的

更改上面的代码,将基类的析构函数声明为虚函数:

class Base{
public:
Base();
virtual ~Base();
protected:
char *str;
};

将基类的析构函数声明为虚函数后,派生类的析构函数也会自动成为虚函数。这个时候编译器会忽略指针的类型,而根据指针的指向来选择函数;也就是说,指针指向哪个类的对象就调用哪个类的函数。pb、pd 都指向了派生类的对象,所以会调用派生类的析构函数,继而再调用基类的析构函数。如此一来也就解决了内存泄露的问题。

当然了,这里强调的是基类,如果一个类是最终的类,那就没必要再声明为虚函数了。

C++语言基础(12)-虚函数的更多相关文章

  1. 第二十三节:Java语言基础-详细讲解函数与数组

    函数 函数在Java中称为方法,在其他语言中可能称为函数,函数,方法就是定义在类中具有特定功能的程序.函数,在Java中可称为方法. 函数的格式: 修饰符 返回值类型 函数名(参数类型 参数1, 参数 ...

  2. &lbrack;11 Go语言基础-可变参数函数&rsqb;

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  3. C&plus;&plus;基础:虚函数、重载、覆盖、隐藏&lt&semi;转&gt&semi;

    转自:http://www.2cto.com/kf/201404/291772.html 虚函数总是跟多态联系在一起,引入虚函数可以使用基类指针对继承类对象进行操作! 虚函数:继承接口(函数名,参数, ...

  4. &lbrack;C&plus;&plus;基础&rsqb; 纯虚函数

    整理摘自https://blog.csdn.net/ithomer/article/details/6031329 1. 申明格式 class CShape { public: ; }; 在普通的虚函 ...

  5. C&plus;&plus;面试常见问题——12虚函数

    虚函数 虚函数的工作原理 虚函数的实现要求对象携带额外的信息,这些信息用于确定运行时调用哪一个虚函数,这一信息具有一种被称为虚函数表指针(vptr)的指针形式.vptr指向一个被称为虚函数表(vtbl ...

  6. GO语言基础map与函数

    1. map 1. 类似其它语言中的哈希表活着字典,以 key-value 形式存储数据 2. key 必须是支持 == 或 != 比较运算的类型,不可以是函数.map 或 slice 3. map ...

  7. C语言基础 &lpar;12&rpar; 文件的操作 FILE

    课程回顾 结构体基本操作: 结构体类型的定义 // struct为关键字 Stu为自定义标识符 // struct Stu才是结构体类型 // 结构体成员不能在定义类型时赋值 struct Stu { ...

  8. Java入门 - 语言基础 - 12&period;Number和Math类

    原文地址:http://www.work100.net/training/java-number-math.html 更多教程:光束云 - 免费课程 Number和Math类 序号 文内章节 视频 1 ...

  9. GO&lowbar;05:GO语言基础map与函数

    1. map 1. 类似其它语言中的哈希表活着字典,以 key-value 形式存储数据 2. key 必须是支持 == 或 != 比较运算的类型,不可以是函数.map 或 slice 3. map ...

随机推荐

  1. Spark读写Hbase的二种方式对比

    作者:Syn良子 出处:http://www.cnblogs.com/cssdongl 转载请注明出处 一.传统方式 这种方式就是常用的TableInputFormat和TableOutputForm ...

  2. HIVE教程

    完整PDF下载:<HIVE简明教程> 前言 Hive是对于数据仓库进行管理和分析的工具.但是不要被“数据仓库”这个词所吓倒,数据仓库是很复杂的东西,但是如果你会SQL,就会发现Hive是那 ...

  3. codevs1403 新三国争霸

    题目描述 Description PP 特别喜欢玩即时战略类游戏,但他觉得那些游戏都有美中不足的地方.灾害总不降临道路,而只降临城市,而且道路不能被占领,没有保护粮草的真实性.于是他就研发了<新 ...

  4. FTP服务器原理

    21.1 FTP服务器原理   使用明码传输方式,且有相当多的安全危机历史.因此一般使用功能较少的vsftpd这个软件.   21.1.1 FTP功能简介   有以下功能 文档传输与管理 不同等级的用 ...

  5. Windows Azure Web Site &lpar;11&rpar; 使用源代码管理器管理Azure Web Site

    <Windows Azure Platform 系列文章目录> 熟悉Azure Web Site平台的读者都知道,我们可以通过FTP等方式,把本地的Web Application部署到微软 ...

  6. linux 系统启动流程

    原著资料网址:http://wenku.baidu.com/view/414127fdf705cc1755270997.html (版权归原作者所有) Linux系统的启动分5个阶段,每个阶段都完成不 ...

  7. python--内建函数&lpar;1&rpar;

    Python中,按照对象是否可变,将类型分类为: 不可变类型:对象的内容不能够改变(not mutable),这些类型中主要有数值类型(整数,浮点数,复数),字符串类型,元组等 可变类型:对象的内容能 ...

  8. pyqt5之简单窗口的创建

    在学完tkinter后,发现tkinter在布局方面特别的不方便(Tkinter资料:http://effbot.org/tkinterbook/tkinter-index.htm),因此学习pyqt ...

  9. WGCNA 分析

    https://www.jianshu.com/p/f80de3468c04 https://mp.weixin.qq.com/s/-DthUKY2RTY6vxtxapzLkw https://www ...

  10. C&plus;&plus;数据结构面试题

    原文:http://1527zhaobin.iteye.com/blog/1537110 一.判断链表是否存在环型链表问题:   说明:判断一个链表是否存在环,例如下面这个链表就存在环,n1--&gt ...