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

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. [11 Go语言基础-可变参数函数]

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

  3. C++基础:虚函数、重载、覆盖、隐藏<转>

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

  4. [C++基础] 纯虚函数

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

  5. C++面试常见问题——12虚函数

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

  6. GO语言基础map与函数

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

  7. C语言基础 (12) 文件的操作 FILE

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

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

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

  9. GO_05:GO语言基础map与函数

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

随机推荐

  1. Spring Struts里用到的设计模式

    Bean工厂的Factory模式 AOP的Proxy模式

  2. [BZOJ 4117] Weather Report

    Link: BZOJ 4117 传送门 Solution: 第一次写$Huffman Tree$相关,发现就是个合并果子? 此题可以将每一种情况的概率和排列总数算出,接下来就是按照$Haffman T ...

  3. 【后缀数组】uoj#35. 后缀排序

    模板 #include<cstdio> #include<algorithm> #include<cstring> using namespace std; #de ...

  4. 【OpenJudge9267】【递推】核电站

    核电站 总时间限制: 5000ms 单个测试点时间限制: 1000ms 内存限制: 131072kB [描述] 一个核电站有N个放核物质的坑,坑排列在一条直线上.如果连续M个坑中放入核物质,则会发生爆 ...

  5. SSH学习——声明式事物管理(Spring)

    1.什么是事物? 事务是一组操作的执行单元,相对于数据库操作来讲,事务管理的是一组SQL指令,比如增加,修改,删除等,事务的一致性,要求,这个事务内的操作必须全部执行成功,如果在此过程种出现了差错,比 ...

  6. [转][译] 分分钟学会一门语言之 Python 篇

    Python was created by Guido Van Rossum in the early 90's. It is now one of the most popularlanguages ...

  7. 狗日的Javascript中的闭包

    前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到 ...

  8. Texygen文本生成,交大计算机系14级的朱耀明

    文本生成哪家强?上交大提出基准测试新平台 Texygen 2018-02-12 13:11测评 新智元报道 来源:arxiv 编译:Marvin [新智元导读]上海交通大学.伦敦大学学院朱耀明, 卢思 ...

  9. 【Cocos2d-x 3.0 基础系列一】 各类回调函数写法汇总

    一.button回调 1. Lambda 表达式,C++11 Lambda 赋予了Cocos2d-x 3.0创建回调函数的灵活性. auto itemNor = Sprite::create(&quo ...

  10. mvn test 中文乱码

    有两种解决办法: 1.设置encoding:<argLine>-Dfile.encoding=UTF-8</argLine>,解决读取文件中的中文乱码问题 2.升级maven- ...