C++中的多态及虚函数大总结
多态是C++中很关键的一部分,在面向对象程序设计中的作用尤为突出,其含义是具有多种形式或形态的情形,简单来说,多态:向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为。即用一个函数名可以调用不同内容的函数。
多态可分为静态多态与动态多态,静态多态的实现在于静态联编,关联出现在编译阶段而非运行期,用对象名或者类名来限定要调用的函数,称为静态关联或静态联编。常见有三种方法
(1)函数多态(函数与运算符的重载);
(2)宏多态;
(3)模板多态。
而对于动态多态的实现是运行阶段把虚函数和类对象绑定在一起的,即动态联编,动态绑定。具体的说,通过一个指向基类的指针调用虚成员函数的时候,运行时系统将能够根据指针所指向的实际对象调用恰当的成员函数实现。
当编译器使用动态绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字,这样的函数称之为虚函数(virtual functions)。根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用派生类的成员函数。如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。 而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。这样,通过基类类型的指针,就可以使属于不同派生类的不同对象产生不同的行为,从而实现运行过程的多态。可看这个例子:
#include <iostream>
using namespace std;
class A
{
public :
void print( ) { cout << “A::print”<<endl ; }
};
class B:public A
{
public :
void print( ) { cout << “B::print” <<endl; }
};
int main( )
{
A a;
B b;
A *pA = &b;
pA->print( );
return 0;
}
此时输出A::print ,若将A类中的print( )函数声明为virtual,则此时就为动态联编 程序执行结果为: B::print。
注意点1:构造函数和静态成员函数不能是虚函数:静态成员函数不能为虚函数,是因为virtual函数由编译器提供了this指针,而静态成员函数没有this指针,是不受限制于某个对象;构造函数不能为虚函数,是因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。
class A
{
public:
virtual A( ) {}; //error
};
class B
{
public:
virtual static void func( ) {}; //error “virtual”不能和“static”一起使用
};
int main( )
{
B b; //报错
A *a=&b;
return 0;
}
注意点2:派生类对象的指针可以直接赋值给基类指针,如上面中的A *a=&b;*a可以看作一个类A的对象,访问它的public成员。通过强制指针类型转换,可以把a转换成B类的指针: a = &b; aa = static_cast< B * > a。此外指向基类的指针,可以指向它的公有派生的对象,但不能指向私有派生的对象,对于引用也是一样的。
class B
{
public:
virtual void print() { cout<<"Hello B"<<endl; }
};
class D : private B
{
public:
virtual void print() { cout<<"Hello D"<<endl; }
};
int main()
{
D d;
B* pb = &d; //转换存在,无法访问
pb->print();
B& rb = d; //转换存在,无法访问
rb.print();
return 0;
}
注意点3:构造函数中调用virtual函数 ,在构造函数和析构函数中调用虚函数时:他们调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。
class Transaction
{
public:
Transaction( ){ logTransaction( ); }
virtual void logTransaction( ) = 0;
};
class BuyTransaction: public Transaction
{
public:
int buyNum;
virtual void logTransaction( ) { cout<< "This is a BuyTransaction"; }
};
class SellTransaction: public Transaction
{
public:
int sellNum;
virtual void logTransaction( )
{
cout<< "This is a SellTransaction";
}
};
int main( )
{
BuyTransaction b;
SellTransaction s;
}
以上代码应该会有报错提示,
若将基类的Transaction中虚函数logTransaction改为:
virtual void logTransaction( ) { cout<< "This is a Transaction"<<endl; };
程序执行结果为: This is a Transaction
This is a Transaction
注意点4:普通成员函数中调用虚函数,在普通成员函数中调用虚函数,则是动态联编,是多态。
#include <iostream>
using namespace std;
class Base
{
public:
void func1( ) { func2( ); }
void virtual func2( ) { cout << "Base::func2( )" << endl; }
};
class Derived:public Base
{
public:
virtual void func2( ) { cout << "Derived:func2( )" << endl; }
};
int main( )
{
Derived d;
Base * pBase = & d;
pBase->func1( );
return 0;
}
因为,Base类的func1( )为非静态成员函数,编译器会给加一个this指针: 相当于 void func1( ) { this->func2( ); } 编译这个函数的代码的时候,由于func2( )是虚函数,this是基类指针,所以是动态联编。上面这个程序运行到func1函数中时, this指针指向的是d,所以经过动态联编,调用的是Derived::func2( )。
注意点5:虚函数的访问权限,如果基类定义的成员虚函数是私有的,我们来看看会怎么样
class Base{
private:
virtual void func( ) { cout << "Base::func( )" << endl; }
};
class Derived : public Base {
public:
virtual void func( ) { cout << "Derived:func( )" << endl; }
};
int main( )
{
Derived d;
Base *pBase = & d;
pBase->func( ); // 无法访问, private 成员(在“Base”类中声明)
return 0;
}
对于类的private成员 ,只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.所以即使是虚函数,也没办法访问。但是!派生类虚函数的可访问性与继承的方式和虚函数在基类的声明方式有关(public,或private)与派生类声明的方式无关(如public继承,但声明为private,但仍可访问),把上面的public与private互换位置,程序可以正常运行,并输出Derived:func( )。
注意点6:虚函数与友元,先看代码
class A;
class B
{
private:
int x;
void print() { cout<<x<<endl; }
public:
B(int i = 0) { x = i; }
friend class A;
};
class A
{
public:
void func(B b){ b.print(); }
};
class C : public A
{
};
class D: public B
{
public:
D(int i):B(i){}
};
int main()
{
D d(99);
A a;
C c;
a.func(d);
c.func(d);
return 0;
}
程序执行结果为:99 99
由第一个99可知,A是B的友元类,A中的所有成员函数都为B的友元函数,可访问B的私有成员函数。友元类A不是基类B的一部分,更不是派生类D的一部分。从上例看,友元视乎能够被继承,基类的友元函数或友元类能够访问派生类的私有成员。但public继承是一种“is a”的关系,即一个派生类对象可看成一个基类对象。所以,上例中不是基类的友元被继承了,而是派生类被识别为基类了。而第二个99说明一个友元类的派生类,可以通过其基类接口去访问设置其基类为友元类的类的私有成员,也就是说一个类的友元类的派生类,某种意义上还是其友元类。
注意点7:析构函数通常是虚函数。虚析构函数保证了在析构时,避免只调用基类析构函数而不调用派生类析构函数的情况,保证资源正常释放,避免了内存释放。只有当一个类被用来作为基类的时候,才会把析构函数写成虚函数。
以上为个人总结,有不妥的地方欢迎指出。
C++中的多态及虚函数大总结的更多相关文章
- C++中的多态与虚函数的内部实现
1.什么是多态 多态性可以简单概括为“一个接口,多种行为”. 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可 ...
- 详解C++中的多态和虚函数
一.将子类赋值给父类 在C++中经常会出现数据类型的转换,比如 int-float等,这种转换的前提是编译器知道如何对数据进行取舍.类其实也是一种数据类型,也可以发生数据转换,但是这种转换只有在 子类 ...
- 【转载】 C++多继承中重写不同基类中相同原型的虚函数
本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...
- C++多态、虚函数、纯虚函数、抽象类、虚基类
一.C++多态 C++的多态包括静态多态和动态多态.静态多态包括函数重载和泛型编程,动态多态包括虚函数.静态多态是指在编译期间就可以确定,动态多态是指在程序运行时才能确定. 二.虚函数 1.虚函数为类 ...
- C++多态、虚函数、纯虚函数、抽象类
多态 同一函数调用形式(调用形式形同)可以实现不同的操作(执行路径不同),就叫多态. 两种多态: (1)静态多态:分为函数重载和运算符重载,编译时系统就能决定调用哪个函数. (2)动态多态(简称多态) ...
- 转 C++中不能声明为虚函数的有哪些函数
传送门 C++中不能声明为虚函数的有哪些函数 常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数 ...
- C++中的继承与虚函数各种概念
虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...
- C++ 基础语法 快速复习笔记(3)---重载函数,多态,虚函数
1.重载运算符和重载函数: C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它 ...
- 《挑战30天C++入门极限》C++中类的多态与虚函数的使用
C++中类的多态与虚函数的使用 类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持 ...
随机推荐
- Redis(四):del/unlink 命令源码解析
上一篇文章从根本上理解了set/get的处理过程,相当于理解了 增.改.查的过程,现在就差一个删了.本篇我们来看一下删除过程. 对于客户端来说,删除操作无需区分何种数据类型,只管进行 del 操作即可 ...
- AttributeError: 'Table' object has no attribute 'plot'错误
今天在用到camelot爬取pdf的表格时,想选取部分区域进行爬取,就想用plot把pdf画一下,选个坐标. 看了网上的示例,在使用camelot.read_pdf获取当前页面以后调用tables[0 ...
- org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3报错解决
报错的原因翻译出来: 预期的一个结果(或null)返回selectOne(),但发现:3 意思就是你想得到一个结果值,但是返回了三个结果值. 一般可能测试的时候我们存了几条一样的数据,在登录时,会把同 ...
- 异步查询转同步加redis业务实现的BUG分享
在最近的性能测试中,某一个查询接口指标不通过,开发做了N次优化,最终的优化方案如下:异步查询然后转同步,再加上redis缓存.此为背景. 在测试过程中发现一个BUG:同样的请求在第一次查询结果是OK的 ...
- pywin32 获取 windows 的窗体内文本框的内容
用 spy++去确认找到了文本框的句柄了. 用函数 win32gui.SendMessage 获取不了文本框的文本内容,用 str 类型的参数接收获取的内容的话没有获取到东西,而用 PyBuffer ...
- spring mvc 框架运行机制 + 数据绑定原理
spring mvc 运行主要的组件: 1 前端控制器 (dispatchservlet) 相当于一个重要处理器,它用来调用其他功能模块来分工的效应一次请求,主要起调度的作用. 2. handler ...
- 关于Windows Server 服务器 安装tomcat部署Java Web 项母
抄至 http://blog.csdn.net/cx0330/article/details/68957914 我遇到的问题是:不知道怎么配置,感觉在服务器上部署一个web项目,应该是很高大上,步骤应 ...
- DRF框架之Serializer序列化器的反序列化操作
昨天,我们完成了Serializer序列化器的反序列化操作,那么今天我们就来学习Serializer序列化器的最后一点知识,反序列化操作. 首先,我们定要明确什么是反序列化操作? 反序列化操作:JOS ...
- 4..部署场景2:带有遗留的Linux Bridge
此场景描述了使用Linux bridge的ML2插件实现OpenStack网络服务的遗留(基本)实现. 遗留实现通过为常规(非特权)用户提供一种方法来管理一个项目中的虚拟网络,并包含以下组件:提供了自 ...
- 时序数据库 Apache-IoTDB 源码解析之前言(一)
IoTDB 是一款时序数据库,相关竞品有 Kairosdb,InfluxDB,TimescaleDB等,主要使用场景是在物联网相关行业,如:车联网.风力发电.地铁.飞机监控等等,具体应用案例及公司详情 ...