C++之成员函数声明为虚函数的规则

虚函数
定义:
虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,是C++多态的一种表现。
作用:
实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型,以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。当程序发现虚函数名前的关键virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。
使用方法:
动态绑定指出,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式:
  指向基类的指针变量名->虚函数名(实参表)

基类对象的引用名. 虚函数名(实参表)

什么函数不能声明为虚函数:
1.构造函数不能声明为虚函数
因为在执行构造函数时类对象还未完成建立过程,更谈不上把函数与类对象的绑定(关于绑定可参考上一篇博文)因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。
2.静态成员函数不能声明为虚函数
(1)静态成员函数并不是“成员”,它没有this指针。
(2)静态成员函数对每个类只有一份代码而且是共享的,所以它不被继承,当然也不能被声明为虚函数。
(3)静态成员函数是静态的在编译时,而虚函数是动态的在运行时。两者创建时间不同。
(4)静态成员函数可以通过类调用,但不能通过对象调用,而虚函数只能通过对象调用才有意义。
3.友元函数不能声明为虚函数
和静态成员类似,它不是成员,不能被继承
4.赋值运算符重载函数最好不要声明为虚函数
5.析构函数可以声明为虚函数
因为无论指针值指的是同一个类中的哪个类对象,系统都会采用动态绑定,调用相应类的析构函数对该对象进行清理。而且继承时最好将析构函数声明为虚函数。
6.普通函数不声明为虚函数
普通函数(非成员函数)声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层函数不属于成员函数,是不能被继承的。
7.内联函数不能声明为虚函数
其实很简单,内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。再者内联函数和虚函数有着本质的区别,内联函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而虚函数是在运行期才能够确定如何去调用的,因而内联函数体现的是一种编译期机制,虚函数体现的是一种运行期机制。此外,一切虚函数都不可能是内联函数。
下面这个示例意在证明将析构函数声明为虚函数的好处:

第一段代码:
用派生类创建派生类指针

#include <iostream>
using namespace std;
    class Base
{
public:
    Base()
    {}
    ~Base()
    {
        cout<<"~Base()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Base"<<endl;
    }
};
class Derived:public Base
{
public:
    Derived()
    {}
    ~Derived()
    {
        cout<<"~Derived()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Derived"<<endl;
    }
};

int main()
{
    Derived *d = new Derived;
    d->Fun1 ();
    delete d;
    return 0;
}

 运行结果:

  Do something in Derived!

  ~Derived()

  ~Base()
这段代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针d的过程是:先释放继承类的资源,再释放基类资源。

若将以上代码的主函数改为用派生类创建基类指针

int main()
{
    Base *b = new Derived;
    b->Fun1 ();
    delete b;
    return 0;
}

 输出结果:

  Do something in Base
  ~Base()
这段代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针b的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.调用Fun1()函数执行的也是基类定义的函数。
一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏。
在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员.如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数。

析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。

#include <iostream>
using namespace std;
class Base
{
public:
    Base()
    {}
    virtual ~Base()
    {
        cout<<"~Base()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Base"<<endl;
    }
};
class Derived:public Base
{
public:
    Derived()
    {}
    ~Derived()
    {
        cout<<"~Derived()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Derived"<<endl;
    }
};

int main()
{
    Base *b = new Derived;
    b->Fun1 ();
    delete b;
    return 0;
}

  运行结果:

  Do something in Derived
  ~Derived()
  ~Base()
这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针b的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用Fun1()函数执行的也是继承类定义的函数。
所以如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
自己整理的C++要背的一些概念,华南师范大学考c++的建议看看,21届考研有考到哦!!(可打印版) 1. 面向对象的三个基本特征 4 2. 抽象 4 3. 如何实现多态性 4 4. const与define的区别 4 5. static作用 4 6. 静态数据成员与静态成员函数 5 7. 静态数据成员与全局变量的不同 5 8. 为什么引入友元的概念? 5 9. 什么是友元函数? 5 10. 友元成员函数 5 11. 友元类 5 12. 函数重载 5 13. 运算符重载 6 14. 运算符重载函数的使用 6 15. 重载运算符的规则 6 16. 为什么要使用继承 6 17. 保护成员的作用 6 18. 多重继承 6 19. 虚基类及其作用 6 20. 派生类的构造函数和析构函数的构造规则 7 21. 虚函数及其作用 7 22. 静态关联和动态关联 7 23. 函数重载与虚函数的不同 7 24. 虚析构函数 8 25. 纯虚函数 8 26. 抽象类 8 27. 抽象类与接口的区别 8 28. 32.输入输出流 8 29. 标准的输出流对象有哪些 8 30. 标准的输入流对象有哪些 9 31. 文件流类与文件流对象 9 32. 两个短整型数相加后,结果是什么类型? 9 33. 什么是值传递? 9 34. 值传递、引用传递和指针传递的区别是什么? 9 35. 什么是函数模板?什么是模板函数?函数模板有什么用途? 9 36. C++是如何实现函数重载的? 10 37. 全局变量和局部变量的主要区别是什么? 使用全局变量有什么好处?有什么坏处? 10 38. 变量定义和变量声明有什么区别? 10 39. 引入结构体有什么好处? 10 40. 单链表为什么要引入头结点? 10 41. 结构体类型定义的作用是什么? 结构体类型的变量定义的作用是什么? 10 42. 用自己的话描述逐步细化的过程 10 43. 为什么库的实现文件要包含自己的头文件? 11 44. 什么头文件要包含 #ifndef...#endif这对编译预处理指令? 11 45. 为什么要使用库? 11 46. 用struct定义类型与用class定义类型有什么区别? 11 47. 构造函数和析构函数的作用是什么?它们各有什么特征? 11 48. 友元的作用是什么? 11 49. 静态数据成员有什么特征?有什么用途? 11 50. 在定义一个类时,哪些部分应放在头文件(.h文件),哪些部分应放在实现文件(.cpp文件)? 12 51. 什么情况下类必须定义自己的复制构造函数? 12 52. 常量数据成员和静态常量数据成员有什么区别?如何初始化常量数据成员?如何初始化静态常量数据成员? 12 53. 什么是this指针?为什么要有this指针? 12 54. 复制构造函数的参数为什么一定要用引用传递,而不能用值传递? 12 55. 构造函数为什么要有初始化列表? 12 56. 如何区分++和--的前缀用法和后缀用法的重载函数? 12 57. 类有几个默认的成员函数? 13 58. 什么是抽象类?定义抽象类有什么意义? 抽象类在使用上有什么限制? 13 59. 为什么要定义虚析构函数? 13 60. 试说明派生类对象的构造和析构次序。 13 61. 试说明虚函数和纯虚函数有什么区别 13 62. 基类指针可以指向派生类的对象, 为什么派生类的指针不能指向基类对象? 13 63. 如果一个派生类新增加的数据成员一个对象成员,试描述派生类的构造过程 13 64. 为什么函数模板的使用与普通的函数完全一样, 而类模板在使用时还必须被实例化? 13 65. 类模板继承时的语法与普通的类继承有什么不同? 13 66. 什么是打开文件?什么是关闭文件?为什么需要打开和关闭文件? 14 67. 为什么要检查文件打开是否成功?如何检查? 14 68. ASCII文件和二进制文件有什么不同? 14 69. C++有哪4个预定义的流? 14 70. 什么时候用输入方式打开文件?什么时候应该用输出方式打开文件?什么时候该用app方式打开文件? 14 71. 哪些流操纵符只对下一次输入/输出有效?哪些流操纵符是一直有效直到被改变? 14 72. 什么叫做作用域?什么叫做局部变量? 什么叫做全局变量? 14

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值