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

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. 「Baltic2015」Network

    题目描述 原文 The government of Byteland has decided that it is time to connect their little country to th ...

  2. 【AC自动机】【动态规划】hdu2296 Ring

    题解:http://www.cnblogs.com/swm8023/archive/2012/08/08/2627535.html 要输出路径,价值最大优先,价值相同的取长度较小者,仍相同取字典序较小 ...

  3. Scala实战高手****第7课:零基础实战Scala面向对象编程及Spark源码解析

    /** * 如果有这些语法的支持,我们说这门语言是支持面向对象的语言 * 其实真正面向对象的精髓是不是封装.继承.多态呢? * --->肯定不是,封装.继承.多态,只不过是支撑面向对象的 * 一 ...

  4. ldr与adr的区别

    参考: http://coon.blogbus.com/logs/2738861.html http://hi.baidu.com/for_guanghui/item/73e60bbcc8be15a2 ...

  5. HTTPS.SYS怎样使用HTTPS

    HTTPS.SYS怎样使用HTTPS 参考了MORMOT的官方文档:http://blog.synopse.info/post/2013/09/04/HTTPS-communication-in-mO ...

  6. devexpress 经验笔记

    1.去除 GridView 头上的 "Drag a column header here to group by that column" -->  点击 Run Desig ...

  7. Linq 简明教程

    一个简单的实例 static void Main(string[] args) { string[] names = { "Alonso", "Zheng", ...

  8. 基于CentOS与VmwareStation10搭建Oracle11G RAC 64集群环境:2.搭建环境-2.9. 配置用户等效性(可选项)

    2.9.配置用户等效性(可选项) Oracle 11g r2 ssh也可以在安装过程中配置. 2.9.1. grid用户等效性 1.以下均以grid用户执行: 在两个节点的grid主目录分别创建.ss ...

  9. Java笔记1:IntelliJ IDEA详细安装步骤

    安装IntelliJ IDEA 一.安装JDK 1 下载最新的jdk,这里下的是jdk-8u66 2 将jdk安装到默认的路径C:\Program Files\Java目录下 二.安装IntelliJ ...

  10. 简单工厂模式 SimpleFactory

     简单工厂模式 SimpleFactory 1.1什么是简单工厂设计模式 简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模 ...