多态是C++中很关键的一部分,在面向对象程序设计中的作用尤为突出,其含义是具有多种形式或形态的情形,简单来说,多态:向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为。即用一个函数名可以调用不同内容的函数。

多态可分为静态多态与动态多态,静态多态的实现在于静态联编,关联出现在编译阶段而非运行期,用对象名或者类名来限定要调用的函数,称为静态关联或静态联编。常见有三种方法

(1)函数多态(函数与运算符的重载);

(2)宏多态;

(3)模板多态。

而对于动态多态的实现是运行阶段把虚函数和类对象绑定在一起的,即动态联编,动态绑定。具体的说,通过一个指向基类的指针调用虚成员函数的时候,运行时系统将能够根据指针所指向的实际对象调用恰当的成员函数实现。

当编译器使用动态绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字,这样的函数称之为虚函数(virtual functions)。根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用派生类的成员函数。如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。 而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。这样,通过基类类型的指针,就可以使属于不同派生类的不同对象产生不同的行为,从而实现运行过程的多态。可看这个例子:

  1. #include <iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public :
  6. void print( ) { cout << A::print”<<endl ; }
  7. };
  8. class B:public A
  9. {
  10. public :
  11. void print( ) { cout << B::print <<endl; }
  12. };
  13. int main( )
  14. {
  15. A a;
  16. B b;
  17. A *pA = &b;
  18. pA->print( );
  19. return 0;
  20. }

  此时输出A::print ,若将A类中的print( )函数声明为virtual,则此时就为动态联编 程序执行结果为: B::print。

 注意点1:构造函数和静态成员函数不能是虚函数:静态成员函数不能为虚函数,是因为virtual函数由编译器提供了this指针,而静态成员函数没有this指针,是不受限制于某个对象;构造函数不能为虚函数,是因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。

  1. class A
  2. {
  3. public:
  4. virtual A( ) {}; //error
  5. };
  6. class B
  7. {
  8. public:
  9. virtual static void func( ) {}; //error “virtual”不能和“static”一起使用
  10. };
  11. int main( )
  12. {

  13. B b //报错
    A *a=&b
  14. return 0;
  15. }

  注意点2:派生类对象的指针可以直接赋值给基类指针,如上面中的A *a=&b;*a可以看作一个类A的对象,访问它的public成员。通过强制指针类型转换,可以把a转换成B类的指针: a = &b; aa = static_cast< B * > a。此外指向基类的指针,可以指向它的公有派生的对象,但不能指向私有派生的对象,对于引用也是一样的。

  1. class B
  2. {
  3. public:
  4. virtual void print() { cout<<"Hello B"<<endl; }
  5. };
  6. class D : private B
  7. {
  8. public:
  9. virtual void print() { cout<<"Hello D"<<endl; }
  10. };
  11. int main()
  12. {
  13. D d;
  14. B* pb = &d; //转换存在,无法访问
  15. pb->print();
  16. B& rb = d; //转换存在,无法访问
  17. rb.print();
  18. return 0;
  19. }

注意点3:构造函数中调用virtual函数 ,在构造函数和析构函数中调用虚函数时:他们调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。

  1. class Transaction
  2. {
  3. public:
  4. Transaction( ){ logTransaction( ); }
  5. virtual void logTransaction( ) = 0;
  6. };
  7. class BuyTransaction: public Transaction
  8. {
  9. public:
  10. int buyNum;
  11. virtual void logTransaction( ) { cout<< "This is a BuyTransaction"; }
  12. };
  13. class SellTransaction: public Transaction
  14. {
  15. public:
  16. int sellNum;
  17. virtual void logTransaction( )
  18. {
  19. cout<< "This is a SellTransaction";
  20. }
  21. };
  22. int main( )
  23. {
  24. BuyTransaction b;
  25. SellTransaction s;
  26. }

  以上代码应该会有报错提示,

若将基类的Transaction中虚函数logTransaction改为:

  1. virtual void logTransaction( )
  2.  
  3. {
  4.  
  5. cout<< "This is a Transaction"<<endl;
  6.  
  7. };

程序执行结果为: This is a Transaction

This is a Transaction

  注意点4:普通成员函数中调用虚函数,在普通成员函数中调用虚函数,则是动态联编,是多态。

  1. #include <iostream>
  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6. void func1( ) { func2( ); }
  7. void virtual func2( ) { cout << "Base::func2( )" << endl; }
  8. };
  9. class Derived:public Base
  10. {
  11. public:
  12. virtual void func2( ) { cout << "Derived:func2( )" << endl; }
  13. };
  14. int main( )
  15. {
  16. Derived d;
  17. Base * pBase = & d;
  18. pBase->func1( );
  19. return 0;
  20. }

因为,Base类的func1( )为非静态成员函数,编译器会给加一个this指针: 相当于 void func1( ) { this->func2( ); } 编译这个函数的代码的时候,由于func2( )是虚函数,this是基类指针,所以是动态联编。上面这个程序运行到func1函数中时, this指针指向的是d,所以经过动态联编,调用的是Derived::func2( )。

注意点5:虚函数的访问权限,如果基类定义的成员虚函数是私有的,我们来看看会怎么样

  1. class Base{
  2. private:
  3. virtual void func( ) { cout << "Base::func( )" << endl; }
  4. };
  5. class Derived : public Base {
  6. public:
  7. virtual void func( ) { cout << "Derived:func( )" << endl; }
  8. };
  9. int main( )
  10. {
  11. Derived d;
  12. Base *pBase = & d;
  13. pBase->func( ); // 无法访问, private 成员(在“Base”类中声明)
  14. return 0;
  15. }

对于类的private成员 ,只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.所以即使是虚函数,也没办法访问。但是!派生类虚函数的可访问性与继承的方式和虚函数在基类的声明方式有关(public,或private)与派生类声明的方式无关(如public继承,但声明为private,但仍可访问),把上面的public与private互换位置,程序可以正常运行,并输出Derived:func( )。

注意点6:虚函数与友元,先看代码

  1. class A;
  2. class B
  3. {
  4. private:
  5. int x;
  6. void print() { cout<<x<<endl; }
  7. public:
  8. B(int i = 0) { x = i; }
  9. friend class A;
  10. };
  11. class A
  12. {
  13. public:
  14. void func(B b){ b.print(); }
  15. };
  16. class C : public A
  17. {
  18. };
  19. class D: public B
  20. {
  21. public:
  22. D(int i):B(i){}
  23. };
  24. int main()
  25. {
  26. D d(99);
  27. A a;
  28. C c;
  29. a.func(d);
  30. c.func(d);
  31. return 0;
  32. }

程序执行结果为:99  99

由第一个99可知,A是B的友元类,A中的所有成员函数都为B的友元函数,可访问B的私有成员函数。友元类A不是基类B的一部分,更不是派生类D的一部分。从上例看,友元视乎能够被继承,基类的友元函数或友元类能够访问派生类的私有成员。但public继承是一种“is a”的关系,即一个派生类对象可看成一个基类对象。所以,上例中不是基类的友元被继承了,而是派生类被识别为基类了。而第二个99说明一个友元类的派生类,可以通过其基类接口去访问设置其基类为友元类的类的私有成员,也就是说一个类的友元类的派生类,某种意义上还是其友元类。

 注意点7:析构函数通常是虚函数。虚析构函数保证了在析构时,避免只调用基类析构函数而不调用派生类析构函数的情况,保证资源正常释放,避免了内存释放。只有当一个类被用来作为基类的时候,才会把析构函数写成虚函数。

    以上为个人总结,有不妥的地方欢迎指出。

C++中的多态及虚函数大总结的更多相关文章

  1. C++中的多态与虚函数的内部实现

    1.什么是多态         多态性可以简单概括为“一个接口,多种行为”.         也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可 ...

  2. 详解C++中的多态和虚函数

    一.将子类赋值给父类 在C++中经常会出现数据类型的转换,比如 int-float等,这种转换的前提是编译器知道如何对数据进行取舍.类其实也是一种数据类型,也可以发生数据转换,但是这种转换只有在 子类 ...

  3. 【转载】 C++多继承中重写不同基类中相同原型的虚函数

    本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...

  4. C++多态、虚函数、纯虚函数、抽象类、虚基类

    一.C++多态 C++的多态包括静态多态和动态多态.静态多态包括函数重载和泛型编程,动态多态包括虚函数.静态多态是指在编译期间就可以确定,动态多态是指在程序运行时才能确定. 二.虚函数 1.虚函数为类 ...

  5. C++多态、虚函数、纯虚函数、抽象类

    多态 同一函数调用形式(调用形式形同)可以实现不同的操作(执行路径不同),就叫多态. 两种多态: (1)静态多态:分为函数重载和运算符重载,编译时系统就能决定调用哪个函数. (2)动态多态(简称多态) ...

  6. 转 C++中不能声明为虚函数的有哪些函数

    传送门 C++中不能声明为虚函数的有哪些函数 常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数 ...

  7. C++中的继承与虚函数各种概念

    虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...

  8. C++ 基础语法 快速复习笔记(3)---重载函数,多态,虚函数

    1.重载运算符和重载函数: C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它 ...

  9. 《挑战30天C++入门极限》C++中类的多态与虚函数的使用

        C++中类的多态与虚函数的使用 类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持 ...

随机推荐

  1. 痞子衡嵌入式:ARM Cortex-M内核那些事(3.2)- 安全模块看特性(M23/33/35P)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是ARM Cortex-M功能模块,不过侧重点是三款安全特性处理器. ARM Cortex-M处理器家族发展至今(2020),已有8代产品 ...

  2. python接口自动化测试 - unittest框架suite、runner详细使用

    test suite 测试套件,理解成测试用例集 一系列的测试用例,或测试套件,理解成测试用例的集合和测试套件的集合 当运行测试套件时,则运行里面添加的所有测试用例 test runner 测试运行器 ...

  3. 机器学习-K最近邻算法

    一.介绍 二.编程 练习一(K最近邻算法在单分类任务的应用): import numpy as np #导入科学计算包import matplotlib.pyplot as plt #导入画图工具fr ...

  4. mezzanine 历险记

    安装去github下载 mezzanine https://github.com/ganmk/mezzanine 安装出现问题了: grappelli_safe >= 0.4.5 问题好像出在这 ...

  5. Windows PHP 开启opcache的方法

    PHP opcache可以提升性能.Windows PHP 配置 opcache 的方法如下: 1.先检查PHP目录下ext目录中有没有php_opcache.dll,没有的话自己下载(PHP 5.5 ...

  6. Linux防火墙之iptables入门

    一.防火墙的概念 什么是防火墙?防火墙是一台或一组设备,用以在网络间实施访问控制策略:事实上一个防火墙能够包含OSI模型中的很多层,并且可能会涉及进行数据包过滤的设备,它可以实施数据包检查和过滤,在更 ...

  7. BeautifulSoup标签定位方法总结

    首先说明一下两个基本函数 .find() 和 .findAll(). find()返回第一个符合要求的标签 findAll()返回一个由所有符合要求的标签组成的列表.除此之外基本相同. 0.直接定位 ...

  8. CORS解决跨域问题的几种方法

    一 后端服务器使用过滤器 新建过滤器: /** * 解决跨域 */ public class AccessControlAllowOriginFilter implements Filter { @O ...

  9. Redis 通用方法 存储DataTable DataRow DataSet

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  10. 用Java实现一个简单的DBMS(总结)

    时间:2020/1/16 写这个DBMS(说DBMS夸张了,应该是一个控制台程序)的起因是数据库实践老师布置的一个大作业,先贴上GitHub地址: https://github.com/machi12 ...